зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to inbound on a CLOSED TREE.
This commit is contained in:
Коммит
d4bc412934
|
@ -12,8 +12,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"revision": "cea79abbb7a97c0bd67051087bcdf40d25611930",
|
||||
"revision": "0fd4065b6621e3b7409cf90ac30d4e174e7317a6",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="788d9ce293a9b44f64536130cf4ad577e8101dbe"/>
|
||||
<project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ce212ba54f36284db84068f82af0c790ceb2c3ff"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ac8a273809b5b160554e616bc5ef2d6fa026ce0e"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="49c722fa1a5e1873fa0010829fd97d0b74009ca5"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="e33ea242b4328fb0d1824c951f379332b5021512"/>
|
||||
|
|
|
@ -564,7 +564,6 @@
|
|||
|
||||
@BINPATH@/components/Payment.js
|
||||
@BINPATH@/components/PaymentFlowInfo.js
|
||||
@BINPATH@/components/PaymentRequestInfo.js
|
||||
@BINPATH@/components/Payment.manifest
|
||||
|
||||
@BINPATH@/components/DownloadsAPI.js
|
||||
|
|
|
@ -1094,12 +1094,18 @@ pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","t
|
|||
pref("devtools.toolbox.sideEnabled", true);
|
||||
pref("devtools.toolbox.zoomValue", "1");
|
||||
|
||||
// Inspector preferences
|
||||
// Enable the Inspector
|
||||
pref("devtools.inspector.enabled", true);
|
||||
// What was the last active sidebar in the inspector
|
||||
pref("devtools.inspector.activeSidebar", "ruleview");
|
||||
// Enable the markup preview
|
||||
pref("devtools.inspector.markupPreview", false);
|
||||
pref("devtools.inspector.remote", false);
|
||||
// Expand pseudo-elements by default in the rule-view
|
||||
pref("devtools.inspector.show_pseudo_elements", true);
|
||||
// The default size for image preview tooltips in the rule-view/computed-view/markup-view
|
||||
pref("devtools.inspector.imagePreviewTooltipSize", 300);
|
||||
|
||||
// DevTools default color unit
|
||||
pref("devtools.defaultColorUnit", "hex");
|
||||
|
|
|
@ -142,18 +142,23 @@ let wrapper = {
|
|||
// Remember who it was so we can log out next time.
|
||||
setPreviousAccountNameHash(newAccountEmail);
|
||||
|
||||
fxAccounts.setSignedInUser(accountData).then(
|
||||
() => {
|
||||
this.injectData("message", { status: "login" });
|
||||
// until we sort out a better UX, just leave the jelly page in place.
|
||||
// If the account email is not yet verified, it will tell the user to
|
||||
// go check their email, but then it will *not* change state after
|
||||
// the verification completes (the browser will begin syncing, but
|
||||
// won't notify the user). If the email has already been verified,
|
||||
// the jelly will say "Welcome! You are successfully signed in as
|
||||
// EMAIL", but it won't then say "syncing started".
|
||||
},
|
||||
(err) => this.injectData("message", { status: "error", error: err })
|
||||
// A sync-specific hack - we want to ensure sync has been initialized
|
||||
// before we set the signed-in user.
|
||||
let xps = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
xps.whenLoaded().then(() => {
|
||||
return fxAccounts.setSignedInUser(accountData);
|
||||
}).then(() => {
|
||||
this.injectData("message", { status: "login" });
|
||||
// until we sort out a better UX, just leave the jelly page in place.
|
||||
// If the account email is not yet verified, it will tell the user to
|
||||
// go check their email, but then it will *not* change state after
|
||||
// the verification completes (the browser will begin syncing, but
|
||||
// won't notify the user). If the email has already been verified,
|
||||
// the jelly will say "Welcome! You are successfully signed in as
|
||||
// EMAIL", but it won't then say "syncing started".
|
||||
}, (err) => this.injectData("message", { status: "error", error: err })
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -152,8 +152,12 @@
|
|||
<panelview id="PanelUI-characterEncodingView" flex="1">
|
||||
<label value="&charsetMenu.label;" class="panel-subview-header"/>
|
||||
|
||||
<vbox id="PanelUI-characterEncodingView-customlist"
|
||||
<vbox id="PanelUI-characterEncodingView-pinned"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
<toolbarseparator/>
|
||||
<vbox id="PanelUI-characterEncodingView-charsets"
|
||||
class="PanelUI-characterEncodingView-list"/>
|
||||
<toolbarseparator/>
|
||||
<vbox>
|
||||
<label id="PanelUI-characterEncodingView-autodetect-label"/>
|
||||
<vbox id="PanelUI-characterEncodingView-autodetect"
|
||||
|
|
|
@ -18,6 +18,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
|
|||
"resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
|
||||
"resource://gre/modules/CharsetMenu.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "CharsetManager",
|
||||
"@mozilla.org/charset-converter-manager;1",
|
||||
"nsICharsetConverterManager");
|
||||
|
@ -650,131 +652,86 @@ const CustomizableWidgets = [{
|
|||
window.gBrowser.docShell &&
|
||||
window.gBrowser.docShell.mayEnableCharacterEncodingMenu);
|
||||
},
|
||||
getCharsetList: function(aSection, aDocument) {
|
||||
let currCharset = aDocument.defaultView.content.document.characterSet;
|
||||
|
||||
let list = "";
|
||||
try {
|
||||
let pref = "intl.charsetmenu.browser." + aSection;
|
||||
list = Services.prefs.getComplexValue(pref,
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) {}
|
||||
|
||||
list = list.trim();
|
||||
if (!list)
|
||||
return [];
|
||||
|
||||
list = list.split(",");
|
||||
|
||||
let items = [];
|
||||
for (let charset of list) {
|
||||
charset = charset.trim();
|
||||
|
||||
let notForBrowser = false;
|
||||
try {
|
||||
notForBrowser = CharsetManager.getCharsetData(charset,
|
||||
"notForBrowser");
|
||||
} catch (e) {}
|
||||
|
||||
if (notForBrowser)
|
||||
continue;
|
||||
|
||||
let title = charset;
|
||||
try {
|
||||
title = CharsetManager.getCharsetTitle(charset);
|
||||
} catch (e) {}
|
||||
|
||||
items.push({value: charset, name: title, current: charset == currCharset});
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
getAutoDetectors: function(aDocument) {
|
||||
let detectorEnum = CharsetManager.GetCharsetDetectorList();
|
||||
let currDetector;
|
||||
try {
|
||||
currDetector = Services.prefs.getComplexValue(
|
||||
"intl.charset.detector", Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) {}
|
||||
if (!currDetector)
|
||||
currDetector = "off";
|
||||
currDetector = "chardet." + currDetector;
|
||||
|
||||
let items = [];
|
||||
|
||||
while (detectorEnum.hasMore()) {
|
||||
let detector = detectorEnum.getNext();
|
||||
|
||||
let title = detector;
|
||||
try {
|
||||
title = CharsetManager.getCharsetTitle(detector);
|
||||
} catch (e) {}
|
||||
|
||||
items.push({value: detector, name: title, current: detector == currDetector});
|
||||
}
|
||||
|
||||
items.sort((aItem1, aItem2) => {
|
||||
return aItem1.name.localeCompare(aItem2.name);
|
||||
});
|
||||
|
||||
return items;
|
||||
},
|
||||
populateList: function(aDocument, aContainerId, aSection) {
|
||||
let containerElem = aDocument.getElementById(aContainerId);
|
||||
|
||||
while (containerElem.firstChild) {
|
||||
containerElem.removeChild(containerElem.firstChild);
|
||||
}
|
||||
|
||||
containerElem.addEventListener("command", this.onCommand, false);
|
||||
|
||||
let list = [];
|
||||
if (aSection == "autodetect") {
|
||||
list = this.getAutoDetectors(aDocument);
|
||||
} else if (aSection == "browser") {
|
||||
let staticList = this.getCharsetList("static", aDocument);
|
||||
let cacheList = this.getCharsetList("cache", aDocument);
|
||||
// Combine lists, and de-duplicate.
|
||||
let checkedIn = new Set();
|
||||
for (let item of staticList.concat(cacheList)) {
|
||||
let itemName = item.name.toLowerCase();
|
||||
if (!checkedIn.has(itemName)) {
|
||||
list.push(item);
|
||||
checkedIn.add(itemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
let list = this.charsetInfo[aSection];
|
||||
|
||||
// Update the appearance of the buttons when it's not possible to
|
||||
// customize encoding.
|
||||
let disabled = this.maybeDisableMenu(aDocument);
|
||||
for (let item of list) {
|
||||
let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||
elem.setAttribute("label", item.name);
|
||||
elem.section = aSection;
|
||||
elem.value = item.value;
|
||||
if (item.current)
|
||||
elem.setAttribute("current", "true");
|
||||
if (disabled)
|
||||
elem.setAttribute("disabled", "true");
|
||||
elem.setAttribute("label", item.label);
|
||||
elem.section = aSection == "detectors" ? "detectors" : "charsets";
|
||||
elem.value = item.id;
|
||||
elem.setAttribute("class", "subviewbutton");
|
||||
containerElem.appendChild(elem);
|
||||
}
|
||||
},
|
||||
updateCurrentCharset: function(aDocument) {
|
||||
let content = aDocument.defaultView.content;
|
||||
let currentCharset = content && content.document && content.document.characterSet;
|
||||
if (currentCharset) {
|
||||
currentCharset = aDocument.defaultView.FoldCharset(currentCharset);
|
||||
}
|
||||
currentCharset = currentCharset ? ("charset." + currentCharset) : "";
|
||||
|
||||
let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned");
|
||||
let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets");
|
||||
let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)];
|
||||
|
||||
this._updateElements(elements, currentCharset);
|
||||
},
|
||||
updateCurrentDetector: function(aDocument) {
|
||||
let detectorContainer = aDocument.getElementById("PanelUI-characterEncodingView-autodetect");
|
||||
let detectorEnum = CharsetManager.GetCharsetDetectorList();
|
||||
let currentDetector;
|
||||
try {
|
||||
currentDetector = Services.prefs.getComplexValue(
|
||||
"intl.charset.detector", Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) {}
|
||||
currentDetector = "chardet." + (currentDetector || "off");
|
||||
|
||||
this._updateElements(detectorContainer.childNodes, currentDetector);
|
||||
},
|
||||
_updateElements: function(aElements, aCurrentItem) {
|
||||
if (!aElements.length) {
|
||||
return;
|
||||
}
|
||||
let disabled = this.maybeDisableMenu(aElements[0].ownerDocument);
|
||||
for (let elem of aElements) {
|
||||
if (disabled) {
|
||||
elem.setAttribute("disabled", "true");
|
||||
} else {
|
||||
elem.removeAttribute("disabled");
|
||||
}
|
||||
if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) {
|
||||
elem.setAttribute("current", "true");
|
||||
} else {
|
||||
elem.removeAttribute("current");
|
||||
}
|
||||
}
|
||||
},
|
||||
onViewShowing: function(aEvent) {
|
||||
let document = aEvent.target.ownerDocument;
|
||||
|
||||
let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label";
|
||||
let autoDetectLabel = document.getElementById(autoDetectLabelId);
|
||||
let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
|
||||
autoDetectLabel.setAttribute("value", label);
|
||||
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-customlist",
|
||||
"browser");
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-autodetect",
|
||||
"autodetect");
|
||||
if (!autoDetectLabel.hasAttribute("value")) {
|
||||
let label = CharsetBundle.GetStringFromName("charsetMenuAutodet");
|
||||
autoDetectLabel.setAttribute("value", label);
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-pinned",
|
||||
"pinnedCharsets");
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-charsets",
|
||||
"otherCharsets");
|
||||
this.populateList(document,
|
||||
"PanelUI-characterEncodingView-autodetect",
|
||||
"detectors");
|
||||
}
|
||||
this.updateCurrentDetector(document);
|
||||
this.updateCurrentCharset(document);
|
||||
},
|
||||
onCommand: function(aEvent) {
|
||||
let node = aEvent.target;
|
||||
|
@ -782,16 +739,16 @@ const CustomizableWidgets = [{
|
|||
return;
|
||||
}
|
||||
|
||||
CustomizableUI.hidePanelForNode(node);
|
||||
let window = node.ownerDocument.defaultView;
|
||||
let section = node.section;
|
||||
let value = node.value;
|
||||
|
||||
// The behavior as implemented here is directly based off of the
|
||||
// `MultiplexHandler()` method in browser.js.
|
||||
if (section == "browser") {
|
||||
window.BrowserSetForcedCharacterSet(value);
|
||||
} else if (section == "autodetect") {
|
||||
if (section != "detectors") {
|
||||
let charset = value.substring(value.indexOf('charset.') + 'charset.'.length);
|
||||
window.BrowserSetForcedCharacterSet(charset);
|
||||
} else {
|
||||
value = value.replace(/^chardet\./, "");
|
||||
if (value == "off") {
|
||||
value = "";
|
||||
|
@ -852,6 +809,9 @@ const CustomizableWidgets = [{
|
|||
}
|
||||
};
|
||||
CustomizableUI.addListener(listener);
|
||||
if (!this.charsetInfo) {
|
||||
this.charsetInfo = CharsetMenu.getData();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: "email-link-button",
|
||||
|
|
|
@ -1060,17 +1060,25 @@ Toolbox.prototype = {
|
|||
let deferred = promise.defer();
|
||||
|
||||
if (this._inspector) {
|
||||
this._selection.destroy();
|
||||
this._selection = null;
|
||||
this._walker.release().then(
|
||||
// Selection is not always available.
|
||||
if (this._selection) {
|
||||
this._selection.destroy();
|
||||
this._selection = null;
|
||||
}
|
||||
|
||||
let walker = this._walker ? this._walker.release() : promise.resolve(null);
|
||||
walker.then(
|
||||
() => {
|
||||
this._inspector.destroy();
|
||||
this._highlighter.destroy();
|
||||
if (this._highlighter) {
|
||||
this._highlighter.destroy();
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
console.error("Walker.release() failed: " + e);
|
||||
this._inspector.destroy();
|
||||
return this._highlighter.destroy();
|
||||
|
||||
return this._highlighter ? this._highlighter.destroy() : promise.resolve(null);
|
||||
}
|
||||
).then(() => {
|
||||
this._inspector = null;
|
||||
|
|
|
@ -101,6 +101,10 @@ InspectorPanel.prototype = {
|
|||
return this._target.client.traits.editOuterHTML;
|
||||
},
|
||||
|
||||
get hasUrlToImageDataResolver() {
|
||||
return this._target.client.traits.urlToImageDataResolver;
|
||||
},
|
||||
|
||||
_deferredOpen: function(defaultSelection) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
|
|||
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
|
||||
const COLLAPSE_DATA_URL_LENGTH = 60;
|
||||
const CONTAINER_FLASHING_DURATION = 500;
|
||||
const IMAGE_PREVIEW_MAX_DIM = 400;
|
||||
const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
|
||||
|
||||
const {UndoStack} = require("devtools/shared/undo");
|
||||
|
@ -1270,16 +1269,17 @@ MarkupContainer.prototype = {
|
|||
data: def.promise
|
||||
};
|
||||
|
||||
this.node.getImageData(IMAGE_PREVIEW_MAX_DIM).then(data => {
|
||||
if (data) {
|
||||
data.data.string().then(str => {
|
||||
let res = {data: str, size: data.size};
|
||||
// Resolving the data promise and, to always keep tooltipData.data
|
||||
// as a promise, create a new one that resolves immediately
|
||||
def.resolve(res);
|
||||
this.tooltipData.data = promise.resolve(res);
|
||||
});
|
||||
}
|
||||
let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
|
||||
this.node.getImageData(maxDim).then(data => {
|
||||
data.data.string().then(str => {
|
||||
let res = {data: str, size: data.size};
|
||||
// Resolving the data promise and, to always keep tooltipData.data
|
||||
// as a promise, create a new one that resolves immediately
|
||||
def.resolve(res);
|
||||
this.tooltipData.data = promise.resolve(res);
|
||||
});
|
||||
}, () => {
|
||||
this.tooltipData.data = promise.reject();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -1288,11 +1288,9 @@ MarkupContainer.prototype = {
|
|||
// We need to send again a request to gettooltipData even if one was sent for
|
||||
// the tooltip, because we want the full-size image
|
||||
this.node.getImageData().then(data => {
|
||||
if (data) {
|
||||
data.data.string().then(str => {
|
||||
clipboardHelper.copyString(str, this.markup.doc);
|
||||
});
|
||||
}
|
||||
data.data.string().then(str => {
|
||||
clipboardHelper.copyString(str, this.markup.doc);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1300,6 +1298,8 @@ MarkupContainer.prototype = {
|
|||
if (this.tooltipData && target === this.tooltipData.target) {
|
||||
this.tooltipData.data.then(({data, size}) => {
|
||||
tooltip.setImageContent(data, size);
|
||||
}, () => {
|
||||
tooltip.setBrokenImageContent();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -846,10 +846,10 @@ var Scratchpad = {
|
|||
|
||||
// Assemble the best possible stack we can given the properties we have.
|
||||
let stack;
|
||||
if (typeof error.stack == "string") {
|
||||
if (typeof error.stack == "string" && error.stack) {
|
||||
stack = error.stack;
|
||||
}
|
||||
else if (typeof error.fileName == "number") {
|
||||
else if (typeof error.fileName == "string") {
|
||||
stack = "@" + error.fileName;
|
||||
if (typeof error.lineNumber == "number") {
|
||||
stack += ":" + error.lineNumber;
|
||||
|
|
|
@ -13,7 +13,7 @@ function test()
|
|||
openScratchpad(runTests, {"state":{"text":""}});
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,<p>test that exceptions our output as " +
|
||||
content.location = "data:text/html,<p>test that exceptions are output as " +
|
||||
"comments for 'display' and not sent to the console in Scratchpad";
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ function runTests()
|
|||
let openComment = "\n/*\n";
|
||||
let closeComment = "\n*/";
|
||||
let error = "throw new Error(\"Ouch!\")";
|
||||
let syntaxError = "(";
|
||||
|
||||
let tests = [{
|
||||
method: "display",
|
||||
|
@ -39,6 +40,13 @@ function runTests()
|
|||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "error display output",
|
||||
},
|
||||
{
|
||||
method: "display",
|
||||
code: syntaxError,
|
||||
result: syntaxError + openComment + "Exception: syntax error\n@" +
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "syntaxError display output",
|
||||
},
|
||||
{
|
||||
method: "run",
|
||||
code: message,
|
||||
|
@ -51,6 +59,13 @@ function runTests()
|
|||
result: error + openComment + "Exception: Ouch!\n@" +
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "error run output",
|
||||
},
|
||||
{
|
||||
method: "run",
|
||||
code: syntaxError,
|
||||
result: syntaxError + openComment + "Exception: syntax error\n@" +
|
||||
scratchpad.uniqueName + ":1" + closeComment,
|
||||
label: "syntaxError run output",
|
||||
}];
|
||||
|
||||
runAsyncTests(scratchpad, tests).then(finish);
|
||||
|
|
|
@ -29,7 +29,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
|
|||
const GRADIENT_RE = /\b(repeating-)?(linear|radial)-gradient\(((rgb|hsl)a?\(.+?\)|[^\)])+\)/gi;
|
||||
const BORDERCOLOR_RE = /^border-[-a-z]*color$/ig;
|
||||
const BORDER_RE = /^border(-(top|bottom|left|right))?$/ig;
|
||||
const BACKGROUND_IMAGE_RE = /url\([\'\"]?(.*?)[\'\"]?\)/;
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const SPECTRUM_FRAME = "chrome://browser/content/devtools/spectrum-frame.xhtml";
|
||||
const ESCAPE_KEYCODE = Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE;
|
||||
|
@ -569,9 +568,45 @@ Tooltip.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with an image, displayed over a tiled background useful
|
||||
* for transparent images. Also adds the image dimension as a label at the
|
||||
* bottom.
|
||||
* Uses the provided inspectorFront's getImageDataFromURL method to resolve
|
||||
* the relative URL on the server-side, in the page context, and then sets the
|
||||
* tooltip content with the resulting image just like |setImageContent| does.
|
||||
*
|
||||
* @return a promise that resolves when the image is shown in the tooltip
|
||||
*/
|
||||
setRelativeImageContent: function(imageUrl, inspectorFront, maxDim) {
|
||||
if (imageUrl.startsWith("data:")) {
|
||||
// If the imageUrl already is a data-url, save ourselves a round-trip
|
||||
this.setImageContent(imageUrl, {maxDim: maxDim});
|
||||
return promise.resolve();
|
||||
} else if (inspectorFront) {
|
||||
return inspectorFront.getImageDataFromURL(imageUrl, maxDim).then(res => {
|
||||
res.size.maxDim = maxDim;
|
||||
return res.data.string().then(str => {
|
||||
this.setImageContent(str, res.size);
|
||||
});
|
||||
}, () => {
|
||||
this.setBrokenImageContent();
|
||||
});
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with a message explaining the the image is missing
|
||||
*/
|
||||
setBrokenImageContent: function() {
|
||||
this.setTextContent({
|
||||
messages: [l10n.strings.GetStringFromName("previewTooltip.image.brokenImage")]
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with an image and add the image dimension at the bottom.
|
||||
*
|
||||
* Only use this for absolute URLs that can be queried from the devtools
|
||||
* client-side. For relative URLs, use |setRelativeImageContent|.
|
||||
*
|
||||
* @param {string} imageUrl
|
||||
* The url to load the image from
|
||||
* @param {Object} options
|
||||
|
@ -585,57 +620,46 @@ Tooltip.prototype = {
|
|||
* a number here
|
||||
*/
|
||||
setImageContent: function(imageUrl, options={}) {
|
||||
// Main container
|
||||
let vbox = this.doc.createElement("vbox");
|
||||
vbox.setAttribute("align", "center");
|
||||
if (imageUrl) {
|
||||
// Main container
|
||||
let vbox = this.doc.createElement("vbox");
|
||||
vbox.setAttribute("align", "center");
|
||||
|
||||
// Display the image
|
||||
let image = this.doc.createElement("image");
|
||||
image.setAttribute("src", imageUrl);
|
||||
if (options.maxDim) {
|
||||
image.style.maxWidth = options.maxDim + "px";
|
||||
image.style.maxHeight = options.maxDim + "px";
|
||||
}
|
||||
vbox.appendChild(image);
|
||||
|
||||
// Dimension label
|
||||
let label = this.doc.createElement("label");
|
||||
label.classList.add("devtools-tooltip-caption");
|
||||
label.classList.add("theme-comment");
|
||||
if (options.naturalWidth && options.naturalHeight) {
|
||||
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
|
||||
options.naturalHeight);
|
||||
} else {
|
||||
// If no dimensions were provided, load the image to get them
|
||||
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
|
||||
let imgObj = new this.doc.defaultView.Image();
|
||||
imgObj.src = imageUrl;
|
||||
imgObj.onload = () => {
|
||||
imgObj.onload = null;
|
||||
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
|
||||
imgObj.naturalHeight);
|
||||
// Display the image
|
||||
let image = this.doc.createElement("image");
|
||||
image.setAttribute("src", imageUrl);
|
||||
if (options.maxDim) {
|
||||
image.style.maxWidth = options.maxDim + "px";
|
||||
image.style.maxHeight = options.maxDim + "px";
|
||||
}
|
||||
}
|
||||
vbox.appendChild(label);
|
||||
vbox.appendChild(image);
|
||||
|
||||
this.content = vbox;
|
||||
// Dimension label
|
||||
let label = this.doc.createElement("label");
|
||||
label.classList.add("devtools-tooltip-caption");
|
||||
label.classList.add("theme-comment");
|
||||
if (options.naturalWidth && options.naturalHeight) {
|
||||
label.textContent = this._getImageDimensionLabel(options.naturalWidth,
|
||||
options.naturalHeight);
|
||||
} else {
|
||||
// If no dimensions were provided, load the image to get them
|
||||
label.textContent = l10n.strings.GetStringFromName("previewTooltip.image.brokenImage");
|
||||
let imgObj = new this.doc.defaultView.Image();
|
||||
imgObj.src = imageUrl;
|
||||
imgObj.onload = () => {
|
||||
imgObj.onload = null;
|
||||
label.textContent = this._getImageDimensionLabel(imgObj.naturalWidth,
|
||||
imgObj.naturalHeight);
|
||||
}
|
||||
}
|
||||
vbox.appendChild(label);
|
||||
|
||||
this.content = vbox;
|
||||
}
|
||||
},
|
||||
|
||||
_getImageDimensionLabel: (w, h) => w + " x " + h,
|
||||
|
||||
/**
|
||||
* Exactly the same as the `image` function but takes a css background image
|
||||
* value instead : url(....)
|
||||
*/
|
||||
setCssBackgroundImageContent: function(cssBackground, sheetHref, maxDim=400) {
|
||||
let uri = getBackgroundImageUri(cssBackground, sheetHref);
|
||||
if (uri) {
|
||||
this.setImageContent(uri, {
|
||||
maxDim: maxDim
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fill the tooltip with a new instance of the spectrum color picker widget
|
||||
* initialized with the given color, and return a promise that resolves to
|
||||
|
@ -941,24 +965,6 @@ function isColorOnly(property, value) {
|
|||
property.match(BORDERCOLOR_RE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal util, returns the background image uri if any
|
||||
*/
|
||||
function getBackgroundImageUri(value, sheetHref) {
|
||||
let uriMatch = BACKGROUND_IMAGE_RE.exec(value);
|
||||
let uri = null;
|
||||
|
||||
if (uriMatch && uriMatch[1]) {
|
||||
uri = uriMatch[1];
|
||||
if (sheetHref) {
|
||||
let sheetUri = IOService.newURI(sheetHref, null, null);
|
||||
uri = sheetUri.resolve(uri);
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* L10N utility class
|
||||
*/
|
||||
|
|
|
@ -6,28 +6,25 @@
|
|||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
|
||||
let ToolDefinitions = require("main").Tools;
|
||||
let {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
let {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
let promise = require("sdk/core/promise");
|
||||
let {EventEmitter} = require("devtools/shared/event-emitter");
|
||||
const ToolDefinitions = require("main").Tools;
|
||||
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
const {ELEMENT_STYLE} = require("devtools/server/actors/styles");
|
||||
const promise = require("sdk/core/promise");
|
||||
const {EventEmitter} = require("devtools/shared/event-emitter");
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
|
||||
const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Templater.jsm");
|
||||
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
|
||||
const FILTER_CHANGED_TIMEOUT = 300;
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
|
||||
/**
|
||||
* Helper for long-running processes that should yield occasionally to
|
||||
* the mainloop.
|
||||
|
@ -183,7 +180,7 @@ function CssHtmlTree(aStyleInspector, aPageStyle)
|
|||
// Properties preview tooltip
|
||||
this.tooltip = new Tooltip(this.styleInspector.inspector.panelDoc);
|
||||
this.tooltip.startTogglingOnHover(this.propertyContainer,
|
||||
this._buildTooltipContent.bind(this));
|
||||
this._onTooltipTargetHover.bind(this));
|
||||
|
||||
this._buildContextMenu();
|
||||
this.createStyleViews();
|
||||
|
@ -514,32 +511,39 @@ CssHtmlTree.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Verify that target is indeed a css value we want a tooltip on, and if yes
|
||||
* prepare some content for the tooltip
|
||||
* Executed by the tooltip when the pointer hovers over an element of the view.
|
||||
* Used to decide whether the tooltip should be shown or not and to actually
|
||||
* put content in it.
|
||||
* Checks if the hovered target is a css value we support tooltips for.
|
||||
*/
|
||||
_buildTooltipContent: function(target)
|
||||
_onTooltipTargetHover: function(target)
|
||||
{
|
||||
let inspector = this.styleInspector.inspector;
|
||||
|
||||
// Test for image url
|
||||
if (target.classList.contains("theme-link")) {
|
||||
if (target.classList.contains("theme-link") && inspector.hasUrlToImageDataResolver) {
|
||||
let propValue = target.parentNode;
|
||||
let propName = propValue.parentNode.querySelector(".property-name");
|
||||
if (propName.textContent === "background-image") {
|
||||
this.tooltip.setCssBackgroundImageContent(propValue.textContent);
|
||||
return true;
|
||||
let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
|
||||
let uri = CssLogic.getBackgroundImageUriFromProperty(propValue.textContent);
|
||||
return this.tooltip.setRelativeImageContent(uri, inspector.inspector, maxDim);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for css transform
|
||||
if (target.classList.contains("property-value")) {
|
||||
let def = promise.defer();
|
||||
let propValue = target;
|
||||
let propName = target.parentNode.querySelector(".property-name");
|
||||
if (propName.textContent === "transform") {
|
||||
this.tooltip.setCssTransformContent(propValue.textContent,
|
||||
this.pageStyle, this.viewedElement).then(def.resolve);
|
||||
return def.promise;
|
||||
return this.tooltip.setCssTransformContent(propValue.textContent,
|
||||
this.pageStyle, this.viewedElement);
|
||||
}
|
||||
}
|
||||
|
||||
// If the target isn't one that should receive a tooltip, signal it by rejecting
|
||||
// a promise
|
||||
return promise.reject();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const promise = require("sdk/core/promise");
|
||||
|
||||
const {CssLogic} = require("devtools/styleinspector/css-logic");
|
||||
const {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
|
||||
const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
|
||||
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip");
|
||||
const {OutputParser} = require("devtools/output-parser");
|
||||
const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -22,8 +22,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
|
||||
|
||||
/**
|
||||
* These regular expressions are adapted from firebug's css.js, and are
|
||||
* used to parse CSSStyleDeclaration's cssText attribute.
|
||||
|
@ -116,8 +114,7 @@ function createDummyDocument() {
|
|||
*
|
||||
* @constructor
|
||||
*/
|
||||
function ElementStyle(aElement, aStore, aPageStyle)
|
||||
{
|
||||
function ElementStyle(aElement, aStore, aPageStyle) {
|
||||
this.element = aElement;
|
||||
this.store = aStore || {};
|
||||
this.pageStyle = aPageStyle;
|
||||
|
@ -142,11 +139,11 @@ function ElementStyle(aElement, aStore, aPageStyle)
|
|||
return this.dummyElement;
|
||||
}).then(null, promiseWarn);
|
||||
}
|
||||
|
||||
// We're exporting _ElementStyle for unit tests.
|
||||
exports._ElementStyle = ElementStyle;
|
||||
|
||||
ElementStyle.prototype = {
|
||||
|
||||
// The element we're looking at.
|
||||
element: null,
|
||||
|
||||
|
@ -154,8 +151,7 @@ ElementStyle.prototype = {
|
|||
// to figure out how shorthand properties will be parsed.
|
||||
dummyElement: null,
|
||||
|
||||
destroy: function()
|
||||
{
|
||||
destroy: function() {
|
||||
this.dummyElement = null;
|
||||
this.dummyElementPromise.then(dummyElement => {
|
||||
if (dummyElement.parentNode) {
|
||||
|
@ -169,8 +165,7 @@ ElementStyle.prototype = {
|
|||
* Called by the Rule object when it has been changed through the
|
||||
* setProperty* methods.
|
||||
*/
|
||||
_changed: function ElementStyle_changed()
|
||||
{
|
||||
_changed: function() {
|
||||
if (this.onChanged) {
|
||||
this.onChanged();
|
||||
}
|
||||
|
@ -183,8 +178,7 @@ ElementStyle.prototype = {
|
|||
* Returns a promise that will be resolved when the elementStyle is
|
||||
* ready.
|
||||
*/
|
||||
populate: function ElementStyle_populate()
|
||||
{
|
||||
populate: function() {
|
||||
let populated = this.pageStyle.getApplied(this.element, {
|
||||
inherited: true,
|
||||
matchedSelectors: true
|
||||
|
@ -224,8 +218,7 @@ ElementStyle.prototype = {
|
|||
/**
|
||||
* Put pseudo elements in front of others.
|
||||
*/
|
||||
_sortRulesForPseudoElement: function ElementStyle_sortRulesForPseudoElement()
|
||||
{
|
||||
_sortRulesForPseudoElement: function() {
|
||||
this.rules = this.rules.sort((a, b) => {
|
||||
return (a.pseudoElement || "z") > (b.pseudoElement || "z");
|
||||
});
|
||||
|
@ -240,8 +233,7 @@ ElementStyle.prototype = {
|
|||
*
|
||||
* @return {bool} true if we added the rule.
|
||||
*/
|
||||
_maybeAddRule: function ElementStyle_maybeAddRule(aOptions)
|
||||
{
|
||||
_maybeAddRule: function(aOptions) {
|
||||
// If we've already included this domRule (for example, when a
|
||||
// common selector is inherited), ignore it.
|
||||
if (aOptions.rule &&
|
||||
|
@ -284,8 +276,7 @@ ElementStyle.prototype = {
|
|||
/**
|
||||
* Calls markOverridden with all supported pseudo elements
|
||||
*/
|
||||
markOverriddenAll: function ElementStyle_markOverriddenAll()
|
||||
{
|
||||
markOverriddenAll: function() {
|
||||
this.markOverridden();
|
||||
for (let pseudo of PSEUDO_ELEMENTS) {
|
||||
this.markOverridden(pseudo);
|
||||
|
@ -299,8 +290,7 @@ ElementStyle.prototype = {
|
|||
* Which pseudo element to flag as overridden.
|
||||
* Empty string or undefined will default to no pseudo element.
|
||||
*/
|
||||
markOverridden: function ElementStyle_markOverridden(pseudo="")
|
||||
{
|
||||
markOverridden: function(pseudo="") {
|
||||
// Gather all the text properties applied by these rules, ordered
|
||||
// from more- to less-specific.
|
||||
let textProps = [];
|
||||
|
@ -380,8 +370,7 @@ ElementStyle.prototype = {
|
|||
* @return {bool} true if the TextProperty's overridden state (or any of its
|
||||
* computed properties overridden state) changed.
|
||||
*/
|
||||
_updatePropertyOverridden: function ElementStyle_updatePropertyOverridden(aProp)
|
||||
{
|
||||
_updatePropertyOverridden: function(aProp) {
|
||||
let overridden = true;
|
||||
let dirty = false;
|
||||
for each (let computedProp in aProp.computed) {
|
||||
|
@ -410,8 +399,7 @@ ElementStyle.prototype = {
|
|||
* the rule applies directly to the current element.
|
||||
* @constructor
|
||||
*/
|
||||
function Rule(aElementStyle, aOptions)
|
||||
{
|
||||
function Rule(aElementStyle, aOptions) {
|
||||
this.elementStyle = aElementStyle;
|
||||
this.domRule = aOptions.rule || null;
|
||||
this.style = aOptions.rule;
|
||||
|
@ -437,8 +425,7 @@ function Rule(aElementStyle, aOptions)
|
|||
Rule.prototype = {
|
||||
mediaText: "",
|
||||
|
||||
get title()
|
||||
{
|
||||
get title() {
|
||||
if (this._title) {
|
||||
return this._title;
|
||||
}
|
||||
|
@ -451,8 +438,7 @@ Rule.prototype = {
|
|||
return this._title;
|
||||
},
|
||||
|
||||
get inheritedSource()
|
||||
{
|
||||
get inheritedSource() {
|
||||
if (this._inheritedSource) {
|
||||
return this._inheritedSource;
|
||||
}
|
||||
|
@ -468,32 +454,28 @@ Rule.prototype = {
|
|||
return this._inheritedSource;
|
||||
},
|
||||
|
||||
get selectorText()
|
||||
{
|
||||
get selectorText() {
|
||||
return this.domRule.selectors ? this.domRule.selectors.join(", ") : CssLogic.l10n("rule.sourceElement");
|
||||
},
|
||||
|
||||
/**
|
||||
* The rule's stylesheet.
|
||||
*/
|
||||
get sheet()
|
||||
{
|
||||
get sheet() {
|
||||
return this.domRule ? this.domRule.parentStyleSheet : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The rule's line within a stylesheet
|
||||
*/
|
||||
get ruleLine()
|
||||
{
|
||||
get ruleLine() {
|
||||
return this.domRule ? this.domRule.line : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* The rule's column within a stylesheet
|
||||
*/
|
||||
get ruleColumn()
|
||||
{
|
||||
get ruleColumn() {
|
||||
return this.domRule ? this.domRule.column : null;
|
||||
},
|
||||
|
||||
|
@ -504,8 +486,7 @@ Rule.prototype = {
|
|||
* @return {Promise}
|
||||
* Promise which resolves with location as a string.
|
||||
*/
|
||||
getOriginalSourceString: function Rule_getOriginalSourceString()
|
||||
{
|
||||
getOriginalSourceString: function() {
|
||||
if (this._originalSourceString) {
|
||||
return promise.resolve(this._originalSourceString);
|
||||
}
|
||||
|
@ -523,8 +504,7 @@ Rule.prototype = {
|
|||
* @param {object} aOptions
|
||||
* Creation options. See the Rule constructor for documentation.
|
||||
*/
|
||||
matches: function Rule_matches(aOptions)
|
||||
{
|
||||
matches: function(aOptions) {
|
||||
return this.style === aOptions.rule;
|
||||
},
|
||||
|
||||
|
@ -540,8 +520,7 @@ Rule.prototype = {
|
|||
* @param {TextProperty} aSiblingProp
|
||||
* Optional, property next to which the new property will be added.
|
||||
*/
|
||||
createProperty: function Rule_createProperty(aName, aValue, aPriority, aSiblingProp)
|
||||
{
|
||||
createProperty: function(aName, aValue, aPriority, aSiblingProp) {
|
||||
let prop = new TextProperty(this, aName, aValue, aPriority);
|
||||
|
||||
if (aSiblingProp) {
|
||||
|
@ -566,8 +545,7 @@ Rule.prototype = {
|
|||
* when calling from setPropertyValue & setPropertyName to signify
|
||||
* that the property should be saved in store.userProperties.
|
||||
*/
|
||||
applyProperties: function Rule_applyProperties(aModifications, aName)
|
||||
{
|
||||
applyProperties: function(aModifications, aName) {
|
||||
this.elementStyle.markOverriddenAll();
|
||||
|
||||
if (!aModifications) {
|
||||
|
@ -652,8 +630,7 @@ Rule.prototype = {
|
|||
* @param {string} aName
|
||||
* The new property name (such as "background" or "border-top").
|
||||
*/
|
||||
setPropertyName: function Rule_setPropertyName(aProperty, aName)
|
||||
{
|
||||
setPropertyName: function(aProperty, aName) {
|
||||
if (aName === aProperty.name) {
|
||||
return;
|
||||
}
|
||||
|
@ -673,8 +650,7 @@ Rule.prototype = {
|
|||
* @param {string} aPriority
|
||||
* The property's priority (either "important" or an empty string).
|
||||
*/
|
||||
setPropertyValue: function Rule_setPropertyValue(aProperty, aValue, aPriority)
|
||||
{
|
||||
setPropertyValue: function(aProperty, aValue, aPriority) {
|
||||
if (aValue === aProperty.value && aPriority === aProperty.priority) {
|
||||
return;
|
||||
}
|
||||
|
@ -687,8 +663,7 @@ Rule.prototype = {
|
|||
/**
|
||||
* Disables or enables given TextProperty.
|
||||
*/
|
||||
setPropertyEnabled: function Rule_enableProperty(aProperty, aValue)
|
||||
{
|
||||
setPropertyEnabled: function(aProperty, aValue) {
|
||||
aProperty.enabled = !!aValue;
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
if (!aProperty.enabled) {
|
||||
|
@ -701,8 +676,7 @@ Rule.prototype = {
|
|||
* Remove a given TextProperty from the rule and update the rule
|
||||
* accordingly.
|
||||
*/
|
||||
removeProperty: function Rule_removeProperty(aProperty)
|
||||
{
|
||||
removeProperty: function(aProperty) {
|
||||
this.textProps = this.textProps.filter(function(prop) prop != aProperty);
|
||||
let modifications = this.style.startModifyingProperties();
|
||||
modifications.removeProperty(aProperty.name);
|
||||
|
@ -715,8 +689,7 @@ Rule.prototype = {
|
|||
* Get the list of TextProperties from the style. Needs
|
||||
* to parse the style's cssText.
|
||||
*/
|
||||
_getTextProperties: function Rule_getTextProperties()
|
||||
{
|
||||
_getTextProperties: function() {
|
||||
let textProps = [];
|
||||
let store = this.elementStyle.store;
|
||||
let props = parseCSSText(this.style.cssText);
|
||||
|
@ -736,8 +709,7 @@ Rule.prototype = {
|
|||
/**
|
||||
* Return the list of disabled properties from the store for this rule.
|
||||
*/
|
||||
_getDisabledProperties: function Rule_getDisabledProperties()
|
||||
{
|
||||
_getDisabledProperties: function() {
|
||||
let store = this.elementStyle.store;
|
||||
|
||||
// Include properties from the disabled property store, if any.
|
||||
|
@ -762,8 +734,7 @@ Rule.prototype = {
|
|||
* Reread the current state of the rules and rebuild text
|
||||
* properties as needed.
|
||||
*/
|
||||
refresh: function Rule_refresh(aOptions)
|
||||
{
|
||||
refresh: function(aOptions) {
|
||||
this.matchedSelectors = aOptions.matchedSelectors || [];
|
||||
let newTextProps = this._getTextProperties();
|
||||
|
||||
|
@ -825,7 +796,7 @@ Rule.prototype = {
|
|||
* @return {bool} true if a property was updated, false if no properties
|
||||
* were updated.
|
||||
*/
|
||||
_updateTextProperty: function Rule__updateTextProperty(aNewProp) {
|
||||
_updateTextProperty: function(aNewProp) {
|
||||
let match = { rank: 0, prop: null };
|
||||
|
||||
for each (let prop in this.textProps) {
|
||||
|
@ -887,8 +858,7 @@ Rule.prototype = {
|
|||
* The text property that will be left to focus on a sibling.
|
||||
*
|
||||
*/
|
||||
editClosestTextProperty: function Rule__editClosestTextProperty(aTextProperty)
|
||||
{
|
||||
editClosestTextProperty: function(aTextProperty) {
|
||||
let index = this.textProps.indexOf(aTextProperty);
|
||||
let previous = false;
|
||||
|
||||
|
@ -930,8 +900,7 @@ Rule.prototype = {
|
|||
* The property's priority (either "important" or an empty string).
|
||||
*
|
||||
*/
|
||||
function TextProperty(aRule, aName, aValue, aPriority)
|
||||
{
|
||||
function TextProperty(aRule, aName, aValue, aPriority) {
|
||||
this.rule = aRule;
|
||||
this.name = aName;
|
||||
this.value = aValue;
|
||||
|
@ -945,8 +914,7 @@ TextProperty.prototype = {
|
|||
* Update the editor associated with this text property,
|
||||
* if any.
|
||||
*/
|
||||
updateEditor: function TextProperty_updateEditor()
|
||||
{
|
||||
updateEditor: function() {
|
||||
if (this.editor) {
|
||||
this.editor.update();
|
||||
}
|
||||
|
@ -955,8 +923,7 @@ TextProperty.prototype = {
|
|||
/**
|
||||
* Update the list of computed properties for this text property.
|
||||
*/
|
||||
updateComputed: function TextProperty_updateComputed()
|
||||
{
|
||||
updateComputed: function() {
|
||||
if (!this.name) {
|
||||
return;
|
||||
}
|
||||
|
@ -988,8 +955,7 @@ TextProperty.prototype = {
|
|||
* @param {TextProperty} aOther
|
||||
* The other TextProperty instance.
|
||||
*/
|
||||
set: function TextProperty_set(aOther)
|
||||
{
|
||||
set: function(aOther) {
|
||||
let changed = false;
|
||||
for (let item of ["name", "value", "priority", "enabled"]) {
|
||||
if (this[item] != aOther[item]) {
|
||||
|
@ -1003,28 +969,24 @@ TextProperty.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
setValue: function TextProperty_setValue(aValue, aPriority)
|
||||
{
|
||||
setValue: function(aValue, aPriority) {
|
||||
this.rule.setPropertyValue(this, aValue, aPriority);
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
setName: function TextProperty_setName(aName)
|
||||
{
|
||||
setName: function(aName) {
|
||||
this.rule.setPropertyName(this, aName);
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
setEnabled: function TextProperty_setEnabled(aValue)
|
||||
{
|
||||
setEnabled: function(aValue) {
|
||||
this.rule.setPropertyEnabled(this, aValue);
|
||||
this.updateEditor();
|
||||
},
|
||||
|
||||
remove: function TextProperty_remove()
|
||||
{
|
||||
remove: function() {
|
||||
this.rule.removeProperty(this);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -1061,8 +1023,7 @@ TextProperty.prototype = {
|
|||
* The PageStyleFront for communicating with the remote server.
|
||||
* @constructor
|
||||
*/
|
||||
function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
|
||||
{
|
||||
function CssRuleView(aInspector, aDoc, aStore, aPageStyle) {
|
||||
this.inspector = aInspector;
|
||||
this.doc = aDoc;
|
||||
this.store = aStore || {};
|
||||
|
@ -1097,7 +1058,7 @@ function CssRuleView(aInspector, aDoc, aStore, aPageStyle)
|
|||
// Create a tooltip for previewing things in the rule view (images for now)
|
||||
this.previewTooltip = new Tooltip(this.inspector.panelDoc);
|
||||
this.previewTooltip.startTogglingOnHover(this.element,
|
||||
this._buildTooltipContent.bind(this));
|
||||
this._onTooltipTargetHover.bind(this));
|
||||
|
||||
// Also create a more complex tooltip for editing colors with the spectrum
|
||||
// color picker
|
||||
|
@ -1149,10 +1110,12 @@ CssRuleView.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Verify that target is indeed a css value we want a tooltip on, and if yes
|
||||
* prepare some content for the tooltip
|
||||
* Executed by the tooltip when the pointer hovers over an element of the view.
|
||||
* Used to decide whether the tooltip should be shown or not and to actually
|
||||
* put content in it.
|
||||
* Checks if the hovered target is a css value we support tooltips for.
|
||||
*/
|
||||
_buildTooltipContent: function(target) {
|
||||
_onTooltipTargetHover: function(target) {
|
||||
let property = target.textProperty, def = promise.defer(), hasTooltip = false;
|
||||
|
||||
// Test for css transform
|
||||
|
@ -1163,19 +1126,26 @@ CssRuleView.prototype = {
|
|||
}
|
||||
|
||||
// Test for image
|
||||
let isImageHref = target.classList.contains("theme-link") &&
|
||||
target.parentNode.classList.contains("ruleview-propertyvalue");
|
||||
if (isImageHref) {
|
||||
property = target.parentNode.textProperty;
|
||||
this.previewTooltip.setCssBackgroundImageContent(property.value,
|
||||
property.rule.domRule.href);
|
||||
def.resolve();
|
||||
hasTooltip = true;
|
||||
if (this.inspector.hasUrlToImageDataResolver) {
|
||||
let isImageHref = target.classList.contains("theme-link") &&
|
||||
target.parentNode.classList.contains("ruleview-propertyvalue");
|
||||
if (isImageHref) {
|
||||
property = target.parentNode.textProperty;
|
||||
|
||||
let maxDim = Services.prefs.getIntPref("devtools.inspector.imagePreviewTooltipSize");
|
||||
let uri = CssLogic.getBackgroundImageUriFromProperty(property.value,
|
||||
property.rule.domRule.href);
|
||||
this.previewTooltip.setRelativeImageContent(uri,
|
||||
this.inspector.inspector, maxDim).then(def.resolve);
|
||||
hasTooltip = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTooltip) {
|
||||
this.colorPicker.revert();
|
||||
this.colorPicker.hide();
|
||||
} else {
|
||||
def.reject();
|
||||
}
|
||||
|
||||
return def.promise;
|
||||
|
@ -1225,8 +1195,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Select all text.
|
||||
*/
|
||||
_onSelectAll: function()
|
||||
{
|
||||
_onSelectAll: function() {
|
||||
let win = this.doc.defaultView;
|
||||
let selection = win.getSelection();
|
||||
|
||||
|
@ -1239,8 +1208,7 @@ CssRuleView.prototype = {
|
|||
* @param {Event} event
|
||||
* The event object.
|
||||
*/
|
||||
_onCopy: function(event)
|
||||
{
|
||||
_onCopy: function(event) {
|
||||
try {
|
||||
let target = event.target;
|
||||
let text;
|
||||
|
@ -1279,8 +1247,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Toggle the original sources pref.
|
||||
*/
|
||||
_onToggleOrigSources: function()
|
||||
{
|
||||
_onToggleOrigSources: function() {
|
||||
let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
|
||||
},
|
||||
|
@ -1305,8 +1272,7 @@ CssRuleView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_onSourcePrefChanged: function()
|
||||
{
|
||||
_onSourcePrefChanged: function() {
|
||||
if (this.menuitemSources) {
|
||||
let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
|
||||
this.menuitemSources.setAttribute("checked", isEnabled);
|
||||
|
@ -1320,8 +1286,7 @@ CssRuleView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
destroy: function CssRuleView_destroy()
|
||||
{
|
||||
destroy: function() {
|
||||
this.clear();
|
||||
|
||||
gDummyPromise = null;
|
||||
|
@ -1378,8 +1343,7 @@ CssRuleView.prototype = {
|
|||
* @param {NodeActor} aElement
|
||||
* The node whose style rules we'll inspect.
|
||||
*/
|
||||
highlight: function CssRuleView_highlight(aElement)
|
||||
{
|
||||
highlight: function(aElement) {
|
||||
if (this._viewedElement === aElement) {
|
||||
return promise.resolve(undefined);
|
||||
}
|
||||
|
@ -1407,8 +1371,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Update the rules for the currently highlighted element.
|
||||
*/
|
||||
nodeChanged: function CssRuleView_nodeChanged()
|
||||
{
|
||||
nodeChanged: function() {
|
||||
// Ignore refreshes during editing or when no element is selected.
|
||||
if (this.isEditing || !this._elementStyle) {
|
||||
return;
|
||||
|
@ -1439,8 +1402,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Show the user that the rule view has no node selected.
|
||||
*/
|
||||
_showEmpty: function CssRuleView_showEmpty()
|
||||
{
|
||||
_showEmpty: function() {
|
||||
if (this.doc.getElementById("noResults") > 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -1454,8 +1416,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Clear the rules.
|
||||
*/
|
||||
_clearRules: function CssRuleView_clearRules()
|
||||
{
|
||||
_clearRules: function() {
|
||||
while (this.element.hasChildNodes()) {
|
||||
this.element.removeChild(this.element.lastChild);
|
||||
}
|
||||
|
@ -1464,8 +1425,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Clear the rule view.
|
||||
*/
|
||||
clear: function CssRuleView_clear()
|
||||
{
|
||||
clear: function() {
|
||||
this._clearRules();
|
||||
this._viewedElement = null;
|
||||
this._elementStyle = null;
|
||||
|
@ -1478,8 +1438,7 @@ CssRuleView.prototype = {
|
|||
* Called when the user has made changes to the ElementStyle.
|
||||
* Emits an event that clients can listen to.
|
||||
*/
|
||||
_changed: function CssRuleView_changed()
|
||||
{
|
||||
_changed: function() {
|
||||
var evt = this.doc.createEvent("Events");
|
||||
evt.initEvent("CssRuleViewChanged", true, false);
|
||||
this.element.dispatchEvent(evt);
|
||||
|
@ -1488,8 +1447,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Text for header that shows above rules for this element
|
||||
*/
|
||||
get selectedElementLabel ()
|
||||
{
|
||||
get selectedElementLabel() {
|
||||
if (this._selectedElementLabel) {
|
||||
return this._selectedElementLabel;
|
||||
}
|
||||
|
@ -1500,8 +1458,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Text for header that shows above rules for pseudo elements
|
||||
*/
|
||||
get pseudoElementLabel ()
|
||||
{
|
||||
get pseudoElementLabel() {
|
||||
if (this._pseudoElementLabel) {
|
||||
return this._pseudoElementLabel;
|
||||
}
|
||||
|
@ -1509,8 +1466,7 @@ CssRuleView.prototype = {
|
|||
return this._pseudoElementLabel;
|
||||
},
|
||||
|
||||
togglePseudoElementVisibility: function(value)
|
||||
{
|
||||
togglePseudoElementVisibility: function(value) {
|
||||
this._showPseudoElements = !!value;
|
||||
let isOpen = this.showPseudoElements;
|
||||
|
||||
|
@ -1529,8 +1485,7 @@ CssRuleView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
get showPseudoElements ()
|
||||
{
|
||||
get showPseudoElements() {
|
||||
if (this._showPseudoElements === undefined) {
|
||||
this._showPseudoElements =
|
||||
Services.prefs.getBoolPref("devtools.inspector.show_pseudo_elements");
|
||||
|
@ -1546,8 +1501,7 @@ CssRuleView.prototype = {
|
|||
/**
|
||||
* Creates editor UI for each of the rules in _elementStyle.
|
||||
*/
|
||||
_createEditors: function CssRuleView_createEditors()
|
||||
{
|
||||
_createEditors: function() {
|
||||
// Run through the current list of rules, attaching
|
||||
// their editors in order. Create editors if needed.
|
||||
let lastInheritedSource = "";
|
||||
|
@ -1606,8 +1560,7 @@ CssRuleView.prototype = {
|
|||
}
|
||||
|
||||
this.togglePseudoElementVisibility(this.showPseudoElements);
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1619,8 +1572,7 @@ CssRuleView.prototype = {
|
|||
* The Rule object we're editing.
|
||||
* @constructor
|
||||
*/
|
||||
function RuleEditor(aRuleView, aRule)
|
||||
{
|
||||
function RuleEditor(aRuleView, aRule) {
|
||||
this.ruleView = aRuleView;
|
||||
this.doc = this.ruleView.doc;
|
||||
this.rule = aRule;
|
||||
|
@ -1632,8 +1584,7 @@ function RuleEditor(aRuleView, aRule)
|
|||
}
|
||||
|
||||
RuleEditor.prototype = {
|
||||
_create: function RuleEditor_create()
|
||||
{
|
||||
_create: function() {
|
||||
this.element = this.doc.createElementNS(HTML_NS, "div");
|
||||
this.element.className = "ruleview-rule theme-separator";
|
||||
this.element._ruleEditor = this;
|
||||
|
@ -1742,8 +1693,7 @@ RuleEditor.prototype = {
|
|||
/**
|
||||
* Update the rule editor with the contents of the rule.
|
||||
*/
|
||||
populate: function RuleEditor_populate()
|
||||
{
|
||||
populate: function() {
|
||||
// Clear out existing viewers.
|
||||
while (this.selectorText.hasChildNodes()) {
|
||||
this.selectorText.removeChild(this.selectorText.lastChild);
|
||||
|
@ -1797,8 +1747,7 @@ RuleEditor.prototype = {
|
|||
* @return {TextProperty}
|
||||
* The new property
|
||||
*/
|
||||
addProperty: function RuleEditor_addProperty(aName, aValue, aPriority, aSiblingProp)
|
||||
{
|
||||
addProperty: function(aName, aValue, aPriority, aSiblingProp) {
|
||||
let prop = this.rule.createProperty(aName, aValue, aPriority, aSiblingProp);
|
||||
let index = this.rule.textProps.indexOf(prop);
|
||||
let editor = new TextPropertyEditor(this, prop);
|
||||
|
@ -1829,8 +1778,7 @@ RuleEditor.prototype = {
|
|||
* @param {TextProperty} aSiblingProp
|
||||
* Optional, the property next to which all new props should be added.
|
||||
*/
|
||||
addProperties: function RuleEditor_addProperties(aProperties, aSiblingProp)
|
||||
{
|
||||
addProperties: function(aProperties, aSiblingProp) {
|
||||
if (!aProperties || !aProperties.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -1853,8 +1801,7 @@ RuleEditor.prototype = {
|
|||
* name is given, we'll create a real TextProperty and add it to the
|
||||
* rule.
|
||||
*/
|
||||
newProperty: function RuleEditor_newProperty()
|
||||
{
|
||||
newProperty: function() {
|
||||
// If we're already creating a new property, ignore this.
|
||||
if (!this.closeBrace.hasAttribute("tabindex")) {
|
||||
return;
|
||||
|
@ -1898,8 +1845,7 @@ RuleEditor.prototype = {
|
|||
* @param {bool} aCommit
|
||||
* True if the value should be committed.
|
||||
*/
|
||||
_onNewProperty: function RuleEditor__onNewProperty(aValue, aCommit)
|
||||
{
|
||||
_onNewProperty: function(aValue, aCommit) {
|
||||
if (!aValue || !aCommit) {
|
||||
return;
|
||||
}
|
||||
|
@ -1924,8 +1870,7 @@ RuleEditor.prototype = {
|
|||
* added, since we want to wait until after the inplace editor `destroy`
|
||||
* event has been fired to keep consistent UI state.
|
||||
*/
|
||||
_newPropertyDestroy: function RuleEditor__newPropertyDestroy()
|
||||
{
|
||||
_newPropertyDestroy: function() {
|
||||
// We're done, make the close brace focusable again.
|
||||
this.closeBrace.setAttribute("tabindex", "0");
|
||||
|
||||
|
@ -1951,8 +1896,7 @@ RuleEditor.prototype = {
|
|||
* The text property to edit.
|
||||
* @constructor
|
||||
*/
|
||||
function TextPropertyEditor(aRuleEditor, aProperty)
|
||||
{
|
||||
function TextPropertyEditor(aRuleEditor, aProperty) {
|
||||
this.ruleEditor = aRuleEditor;
|
||||
this.doc = this.ruleEditor.doc;
|
||||
this.popup = this.ruleEditor.ruleView.popup;
|
||||
|
@ -1991,8 +1935,7 @@ TextPropertyEditor.prototype = {
|
|||
/**
|
||||
* Create the property editor's DOM.
|
||||
*/
|
||||
_create: function TextPropertyEditor_create()
|
||||
{
|
||||
_create: function() {
|
||||
this.element = this.doc.createElementNS(HTML_NS, "li");
|
||||
this.element.classList.add("ruleview-property");
|
||||
|
||||
|
@ -2119,8 +2062,7 @@ TextPropertyEditor.prototype = {
|
|||
* @param {string} relativePath the path to resolve
|
||||
* @return {string} the resolved path.
|
||||
*/
|
||||
resolveURI: function(relativePath)
|
||||
{
|
||||
resolveURI: function(relativePath) {
|
||||
if (this.sheetURI) {
|
||||
relativePath = this.sheetURI.resolve(relativePath);
|
||||
}
|
||||
|
@ -2131,8 +2073,7 @@ TextPropertyEditor.prototype = {
|
|||
* Check the property value to find an external resource (if any).
|
||||
* @return {string} the URI in the property value, or null if there is no match.
|
||||
*/
|
||||
getResourceURI: function()
|
||||
{
|
||||
getResourceURI: function() {
|
||||
let val = this.prop.value;
|
||||
let uriMatch = CSS_RESOURCE_RE.exec(val);
|
||||
let uri = null;
|
||||
|
@ -2147,8 +2088,7 @@ TextPropertyEditor.prototype = {
|
|||
/**
|
||||
* Populate the span based on changes to the TextProperty.
|
||||
*/
|
||||
update: function TextPropertyEditor_update()
|
||||
{
|
||||
update: function() {
|
||||
if (this.prop.enabled) {
|
||||
this.enable.style.removeProperty("visibility");
|
||||
this.enable.setAttribute("checked", "");
|
||||
|
@ -2216,8 +2156,7 @@ TextPropertyEditor.prototype = {
|
|||
this._updateComputed();
|
||||
},
|
||||
|
||||
_onStartEditing: function TextPropertyEditor_onStartEditing()
|
||||
{
|
||||
_onStartEditing: function() {
|
||||
this.element.classList.remove("ruleview-overridden");
|
||||
this._livePreview(this.prop.value);
|
||||
},
|
||||
|
@ -2225,8 +2164,7 @@ TextPropertyEditor.prototype = {
|
|||
/**
|
||||
* Populate the list of computed styles.
|
||||
*/
|
||||
_updateComputed: function TextPropertyEditor_updateComputed()
|
||||
{
|
||||
_updateComputed: function () {
|
||||
// Clear out existing viewers.
|
||||
while (this.computed.hasChildNodes()) {
|
||||
this.computed.removeChild(this.computed.lastChild);
|
||||
|
@ -2284,8 +2222,7 @@ TextPropertyEditor.prototype = {
|
|||
/**
|
||||
* Handles clicks on the disabled property.
|
||||
*/
|
||||
_onEnableClicked: function TextPropertyEditor_onEnableClicked(aEvent)
|
||||
{
|
||||
_onEnableClicked: function(aEvent) {
|
||||
let checked = this.enable.hasAttribute("checked");
|
||||
if (checked) {
|
||||
this.enable.removeAttribute("checked");
|
||||
|
@ -2299,8 +2236,7 @@ TextPropertyEditor.prototype = {
|
|||
/**
|
||||
* Handles clicks on the computed property expander.
|
||||
*/
|
||||
_onExpandClicked: function TextPropertyEditor_onExpandClicked(aEvent)
|
||||
{
|
||||
_onExpandClicked: function(aEvent) {
|
||||
this.computed.classList.toggle("styleinspector-open");
|
||||
if (this.computed.classList.contains("styleinspector-open")) {
|
||||
this.expander.setAttribute("open", "true");
|
||||
|
@ -2320,8 +2256,7 @@ TextPropertyEditor.prototype = {
|
|||
* @param {boolean} aCommit
|
||||
* True if the change should be applied.
|
||||
*/
|
||||
_onNameDone: function TextPropertyEditor_onNameDone(aValue, aCommit)
|
||||
{
|
||||
_onNameDone: function(aValue, aCommit) {
|
||||
if (aCommit) {
|
||||
// Unlike the value editor, if a name is empty the entire property
|
||||
// should always be removed.
|
||||
|
@ -2349,8 +2284,7 @@ TextPropertyEditor.prototype = {
|
|||
* Remove property from style and the editors from DOM.
|
||||
* Begin editing next available property.
|
||||
*/
|
||||
remove: function TextPropertyEditor_remove()
|
||||
{
|
||||
remove: function() {
|
||||
if (this._swatchSpans && this._swatchSpans.length) {
|
||||
for (let span of this._swatchSpans) {
|
||||
this.ruleEditor.ruleView.colorPicker.removeSwatch(span);
|
||||
|
@ -2372,8 +2306,7 @@ TextPropertyEditor.prototype = {
|
|||
* @param {bool} aCommit
|
||||
* True if the change should be applied.
|
||||
*/
|
||||
_onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
|
||||
{
|
||||
_onValueDone: function(aValue, aCommit) {
|
||||
if (!aCommit) {
|
||||
// A new property should be removed when escape is pressed.
|
||||
if (this.removeOnRevert) {
|
||||
|
@ -2425,8 +2358,7 @@ TextPropertyEditor.prototype = {
|
|||
* propertiesToAdd: An array with additional properties, following the
|
||||
* parseCSSText format of {name,value,priority}
|
||||
*/
|
||||
_getValueAndExtraProperties: function PropetyEditor_getValueAndExtraProperties(aValue) {
|
||||
|
||||
_getValueAndExtraProperties: function(aValue) {
|
||||
// The inplace editor will prevent manual typing of multiple properties,
|
||||
// but we need to deal with the case during a paste event.
|
||||
// Adding multiple properties inside of value editor sets value with the
|
||||
|
@ -2462,8 +2394,7 @@ TextPropertyEditor.prototype = {
|
|||
};
|
||||
},
|
||||
|
||||
_applyNewValue: function PropetyEditor_applyNewValue(aValue)
|
||||
{
|
||||
_applyNewValue: function(aValue) {
|
||||
let val = parseCSSValue(aValue);
|
||||
// Any property should be removed if has an empty value.
|
||||
if (val.value.trim() === "") {
|
||||
|
@ -2482,8 +2413,7 @@ TextPropertyEditor.prototype = {
|
|||
* @param {string} [aValue]
|
||||
* The value to set the current property to.
|
||||
*/
|
||||
_livePreview: function TextPropertyEditor_livePreview(aValue)
|
||||
{
|
||||
_livePreview: function(aValue) {
|
||||
// Since function call is throttled, we need to make sure we are still editing
|
||||
if (!this.editing) {
|
||||
return;
|
||||
|
@ -2506,8 +2436,7 @@ TextPropertyEditor.prototype = {
|
|||
*
|
||||
* @return {bool} true if the property value is valid, false otherwise.
|
||||
*/
|
||||
isValid: function TextPropertyEditor_isValid(aValue)
|
||||
{
|
||||
isValid: function(aValue) {
|
||||
let name = this.prop.name;
|
||||
let value = typeof aValue == "undefined" ? this.prop.value : aValue;
|
||||
let val = parseCSSValue(value);
|
||||
|
@ -2534,8 +2463,7 @@ TextPropertyEditor.prototype = {
|
|||
* Store of CSSStyleDeclarations mapped to properties that have been changed by
|
||||
* the user.
|
||||
*/
|
||||
function UserProperties()
|
||||
{
|
||||
function UserProperties() {
|
||||
this.map = new Map();
|
||||
}
|
||||
|
||||
|
@ -2607,7 +2535,7 @@ UserProperties.prototype = {
|
|||
|
||||
getKey: function(aStyle) {
|
||||
return aStyle.href + ":" + aStyle.line;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2624,8 +2552,7 @@ UserProperties.prototype = {
|
|||
* @param {object} aAttributes
|
||||
* A set of attributes to set on the node.
|
||||
*/
|
||||
function createChild(aParent, aTag, aAttributes)
|
||||
{
|
||||
function createChild(aParent, aTag, aAttributes) {
|
||||
let elt = aParent.ownerDocument.createElementNS(HTML_NS, aTag);
|
||||
for (let attr in aAttributes) {
|
||||
if (aAttributes.hasOwnProperty(attr)) {
|
||||
|
@ -2642,8 +2569,7 @@ function createChild(aParent, aTag, aAttributes)
|
|||
return elt;
|
||||
}
|
||||
|
||||
function createMenuItem(aMenu, aAttributes)
|
||||
{
|
||||
function createMenuItem(aMenu, aAttributes) {
|
||||
let item = aMenu.ownerDocument.createElementNS(XUL_NS, "menuitem");
|
||||
|
||||
item.setAttribute("label", _strings.GetStringFromName(aAttributes.label));
|
||||
|
@ -2655,20 +2581,17 @@ function createMenuItem(aMenu, aAttributes)
|
|||
return item;
|
||||
}
|
||||
|
||||
function setTimeout()
|
||||
{
|
||||
function setTimeout() {
|
||||
let window = Services.appShell.hiddenDOMWindow;
|
||||
return window.setTimeout.apply(window, arguments);
|
||||
}
|
||||
|
||||
function clearTimeout()
|
||||
{
|
||||
function clearTimeout() {
|
||||
let window = Services.appShell.hiddenDOMWindow;
|
||||
return window.clearTimeout.apply(window, arguments);
|
||||
}
|
||||
|
||||
function throttle(func, wait, scope)
|
||||
{
|
||||
function throttle(func, wait, scope) {
|
||||
var timer = null;
|
||||
return function() {
|
||||
if(timer) {
|
||||
|
@ -2690,8 +2613,7 @@ function throttle(func, wait, scope)
|
|||
* The value from the text editor.
|
||||
* @return {object} an object with 'value' and 'priority' properties.
|
||||
*/
|
||||
function parseCSSValue(aValue)
|
||||
{
|
||||
function parseCSSValue(aValue) {
|
||||
let pieces = aValue.split("!", 2);
|
||||
return {
|
||||
value: pieces[0].trim(),
|
||||
|
@ -2709,8 +2631,7 @@ function parseCSSValue(aValue)
|
|||
* @return {Array} an array of objects with the following signature:
|
||||
* [{"name": string, "value": string, "priority": string}, ...]
|
||||
*/
|
||||
function parseCSSText(aCssText)
|
||||
{
|
||||
function parseCSSText(aCssText) {
|
||||
let lines = aCssText.match(CSS_LINE_RE);
|
||||
let props = [];
|
||||
|
||||
|
@ -2739,8 +2660,7 @@ function parseCSSText(aCssText)
|
|||
* Event handler that causes a blur on the target if the input has
|
||||
* multiple CSS properties as the value.
|
||||
*/
|
||||
function blurOnMultipleProperties(e)
|
||||
{
|
||||
function blurOnMultipleProperties(e) {
|
||||
setTimeout(() => {
|
||||
if (parseCSSText(e.target.value).length) {
|
||||
e.target.blur();
|
||||
|
@ -2751,8 +2671,7 @@ function blurOnMultipleProperties(e)
|
|||
/**
|
||||
* Append a text node to an element.
|
||||
*/
|
||||
function appendText(aParent, aText)
|
||||
{
|
||||
function appendText(aParent, aText) {
|
||||
aParent.appendChild(aParent.ownerDocument.createTextNode(aText));
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ function testDivRuleView() {
|
|||
assertTooltipShownOn(ruleView.previewTooltip, uriSpan, () => {
|
||||
let images = panel.getElementsByTagName("image");
|
||||
is(images.length, 1, "Tooltip contains an image");
|
||||
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
|
||||
ok(images[0].src.startsWith("data:"), "Tooltip contains a data-uri image as expected");
|
||||
|
||||
ruleView.previewTooltip.hide();
|
||||
|
||||
|
@ -147,7 +147,7 @@ function testComputedView() {
|
|||
assertTooltipShownOn(computedView.tooltip, uriSpan, () => {
|
||||
let images = panel.getElementsByTagName("image");
|
||||
is(images.length, 1, "Tooltip contains an image");
|
||||
ok(images[0].src === "chrome://global/skin/icons/warning-64.png");
|
||||
ok(images[0].src.startsWith("data:"), "Tooltip contains a data-uri in the computed-view too");
|
||||
|
||||
computedView.tooltip.hide();
|
||||
|
||||
|
|
|
@ -16,12 +16,28 @@ const URL_CLASS = "url-class";
|
|||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
function countAll(fragment) {
|
||||
return fragment.querySelectorAll("*").length;
|
||||
}
|
||||
function countColors(fragment) {
|
||||
return fragment.querySelectorAll("." + COLOR_CLASS).length;
|
||||
}
|
||||
function countUrls(fragment) {
|
||||
return fragment.querySelectorAll("." + URL_CLASS).length;
|
||||
}
|
||||
function getColor(fragment, index) {
|
||||
return fragment.querySelectorAll("." + COLOR_CLASS)[index||0].textContent;
|
||||
}
|
||||
function getUrl(fragment, index) {
|
||||
return fragment.querySelectorAll("." + URL_CLASS)[index||0].textContent;
|
||||
}
|
||||
|
||||
let testData = [
|
||||
{
|
||||
name: "width",
|
||||
value: "100%",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
is(fragment.textContent, "100%");
|
||||
}
|
||||
},
|
||||
|
@ -29,36 +45,36 @@ function test() {
|
|||
name: "width",
|
||||
value: "blue",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
value: "'red url(test.png) repeat top left'",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "content",
|
||||
value: "\"blue\"",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "margin-left",
|
||||
value: "url(something.jpg)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 0);
|
||||
is(countAll(fragment), 0);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background-color",
|
||||
value: "transparent",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countAll(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "transparent");
|
||||
}
|
||||
},
|
||||
|
@ -66,7 +82,7 @@ function test() {
|
|||
name: "color",
|
||||
value: "red",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "red");
|
||||
}
|
||||
},
|
||||
|
@ -74,7 +90,7 @@ function test() {
|
|||
name: "color",
|
||||
value: "#F06",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "#F06");
|
||||
}
|
||||
},
|
||||
|
@ -82,8 +98,8 @@ function test() {
|
|||
name: "border-top-left-color",
|
||||
value: "rgba(14, 255, 20, .5)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countAll(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "rgba(14, 255, 20, .5)");
|
||||
}
|
||||
},
|
||||
|
@ -91,16 +107,16 @@ function test() {
|
|||
name: "border",
|
||||
value: "80em dotted pink",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + COLOR_CLASS).textContent, "pink");
|
||||
is(countAll(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(getColor(fragment), "pink");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
value: "red !important",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(countColors(fragment), 1);
|
||||
is(fragment.textContent, "red !important");
|
||||
}
|
||||
},
|
||||
|
@ -108,38 +124,38 @@ function test() {
|
|||
name: "background",
|
||||
value: "red url(test.png) repeat top left",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(fragment.querySelectorAll("." + URL_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + COLOR_CLASS).textContent, "red");
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "test.png");
|
||||
is(fragment.querySelectorAll("*").length, 2);
|
||||
is(countColors(fragment), 1);
|
||||
is(countUrls(fragment), 1);
|
||||
is(getColor(fragment), "red");
|
||||
is(getUrl(fragment), "test.png");
|
||||
is(countAll(fragment), 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "blue url(test.png) repeat top left !important",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("." + COLOR_CLASS).length, 1);
|
||||
is(fragment.querySelectorAll("." + URL_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + COLOR_CLASS).textContent, "blue");
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "test.png");
|
||||
is(fragment.querySelectorAll("*").length, 2);
|
||||
is(countColors(fragment), 1);
|
||||
is(countUrls(fragment), 1);
|
||||
is(getColor(fragment), "blue");
|
||||
is(getUrl(fragment), "test.png");
|
||||
is(countAll(fragment), 2);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "list-style-image",
|
||||
value: "url(\"images/arrow.gif\")",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "images/arrow.gif");
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "images/arrow.gif");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "list-style-image",
|
||||
value: "url(\"images/arrow.gif\")!important",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "images/arrow.gif");
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "images/arrow.gif");
|
||||
is(fragment.textContent, "url('images/arrow.gif')!important");
|
||||
}
|
||||
},
|
||||
|
@ -147,16 +163,16 @@ function test() {
|
|||
name: "-moz-binding",
|
||||
value: "url(http://somesite.com/path/to/binding.xml#someid)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 1);
|
||||
is(fragment.querySelectorAll("." + URL_CLASS).length, 1);
|
||||
is(fragment.querySelector("." + URL_CLASS).textContent, "http://somesite.com/path/to/binding.xml#someid");
|
||||
is(countAll(fragment), 1);
|
||||
is(countUrls(fragment), 1);
|
||||
is(getUrl(fragment), "http://somesite.com/path/to/binding.xml#someid");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "linear-gradient(to right, rgba(183,222,237,1) 0%, rgba(33,180,226,1) 30%, rgba(31,170,217,.5) 44%, #F06 75%, red 100%)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 5);
|
||||
is(countAll(fragment), 5);
|
||||
let allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
|
||||
is(allSwatches.length, 5);
|
||||
is(allSwatches[0].textContent, "rgba(183,222,237,1)");
|
||||
|
@ -170,12 +186,54 @@ function test() {
|
|||
name: "background",
|
||||
value: "-moz-radial-gradient(center 45deg, circle closest-side, orange 0%, red 100%)",
|
||||
test: fragment => {
|
||||
is(fragment.querySelectorAll("*").length, 2);
|
||||
is(countAll(fragment), 2);
|
||||
let allSwatches = fragment.querySelectorAll("." + COLOR_CLASS);
|
||||
is(allSwatches.length, 2);
|
||||
is(allSwatches[0].textContent, "orange");
|
||||
is(allSwatches[1].textContent, "red");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "white url(http://test.com/wow_such_image.png) no-repeat top left",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 2);
|
||||
is(countUrls(fragment), 1);
|
||||
is(countColors(fragment), 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "url(\"http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t\")",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "http://test.com/wow_such_(oh-noes)image.png?testid=1&color=red#w00t");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background-image",
|
||||
value: "url(this-is-an-incredible-image.jpeg)",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "this-is-an-incredible-image.jpeg");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background",
|
||||
value: "red url( \"http://wow.com/cool/../../../you're(doingit)wrong\" ) repeat center",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 2);
|
||||
is(countColors(fragment), 1);
|
||||
is(getUrl(fragment), "http://wow.com/cool/../../../you're(doingit)wrong");
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "background-image",
|
||||
value: "url(../../../look/at/this/folder/structure/../../red.blue.green.svg )",
|
||||
test: fragment => {
|
||||
is(countAll(fragment), 1);
|
||||
is(getUrl(fragment), "../../../look/at/this/folder/structure/../../red.blue.green.svg");
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -569,7 +569,6 @@
|
|||
|
||||
@BINPATH@/components/Payment.js
|
||||
@BINPATH@/components/PaymentFlowInfo.js
|
||||
@BINPATH@/components/PaymentRequestInfo.js
|
||||
@BINPATH@/components/Payment.manifest
|
||||
|
||||
#ifdef MOZ_WEBRTC
|
||||
|
|
|
@ -106,6 +106,8 @@ var BrowserUI = {
|
|||
window.addEventListener("MozPrecisePointer", this, true);
|
||||
window.addEventListener("MozImprecisePointer", this, true);
|
||||
|
||||
window.addEventListener("AppCommand", this, true);
|
||||
|
||||
Services.prefs.addObserver("browser.cache.disk_cache_ssl", this, false);
|
||||
|
||||
// Init core UI modules
|
||||
|
@ -781,6 +783,9 @@ var BrowserUI = {
|
|||
case "MozImprecisePointer":
|
||||
this._onImpreciseInput();
|
||||
break;
|
||||
case "AppCommand":
|
||||
this.handleAppCommandEvent(aEvent);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1140,6 +1145,48 @@ var BrowserUI = {
|
|||
}
|
||||
},
|
||||
|
||||
handleAppCommandEvent: function (aEvent) {
|
||||
switch (aEvent.command) {
|
||||
case "Back":
|
||||
this.doCommand("cmd_back");
|
||||
break;
|
||||
case "Forward":
|
||||
this.doCommand("cmd_forward");
|
||||
break;
|
||||
case "Reload":
|
||||
this.doCommand("cmd_reload");
|
||||
break;
|
||||
case "Stop":
|
||||
this.doCommand("cmd_stop");
|
||||
break;
|
||||
case "Home":
|
||||
this.doCommand("cmd_home");
|
||||
break;
|
||||
case "New":
|
||||
this.doCommand("cmd_newTab");
|
||||
break;
|
||||
case "Close":
|
||||
this.doCommand("cmd_closeTab");
|
||||
break;
|
||||
case "Find":
|
||||
FindHelperUI.show();
|
||||
break;
|
||||
case "Open":
|
||||
this.doCommand("cmd_openFile");
|
||||
break;
|
||||
case "Save":
|
||||
this.doCommand("cmd_savePage");
|
||||
break;
|
||||
case "Search":
|
||||
this.doCommand("cmd_openLocation");
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
},
|
||||
|
||||
confirmSanitizeDialog: function () {
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
let title = bundle.GetStringFromName("clearPrivateData.title2");
|
||||
|
|
|
@ -323,6 +323,7 @@ this.UITour = {
|
|||
|
||||
teardownTour: function(aWindow, aWindowClosing = false) {
|
||||
aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
aWindow.PanelUI.panel.removeEventListener("popuphiding", this.onAppMenuHiding);
|
||||
aWindow.removeEventListener("SSWindowClosing", this);
|
||||
|
||||
let originTabs = this.originTabs.get(aWindow);
|
||||
|
@ -432,7 +433,10 @@ this.UITour = {
|
|||
}
|
||||
|
||||
if (aTargetName == "pinnedTab") {
|
||||
deferred.resolve({node: this.ensurePinnedTab(aWindow, aSticky)});
|
||||
deferred.resolve({
|
||||
targetName: aTargetName,
|
||||
node: this.ensurePinnedTab(aWindow, aSticky)
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
@ -446,6 +450,7 @@ this.UITour = {
|
|||
aWindow.PanelUI.ensureReady().then(() => {
|
||||
if (typeof targetQuery == "function") {
|
||||
deferred.resolve({
|
||||
targetName: aTargetName,
|
||||
node: targetQuery(aWindow.document),
|
||||
widgetName: targetObject.widgetName,
|
||||
});
|
||||
|
@ -453,6 +458,7 @@ this.UITour = {
|
|||
}
|
||||
|
||||
deferred.resolve({
|
||||
targetName: aTargetName,
|
||||
node: aWindow.document.querySelector(targetQuery),
|
||||
widgetName: targetObject.widgetName,
|
||||
});
|
||||
|
@ -573,12 +579,26 @@ this.UITour = {
|
|||
effect = this.highlightEffects[randomEffect];
|
||||
}
|
||||
highlighter.setAttribute("active", effect);
|
||||
highlighter.parentElement.setAttribute("targetName", aTarget.targetName);
|
||||
highlighter.parentElement.hidden = false;
|
||||
|
||||
let targetRect = aTargetEl.getBoundingClientRect();
|
||||
let highlightHeight = targetRect.height;
|
||||
let highlightWidth = targetRect.width;
|
||||
let minDimension = Math.min(highlightHeight, highlightWidth);
|
||||
let maxDimension = Math.max(highlightHeight, highlightWidth);
|
||||
|
||||
highlighter.style.height = targetRect.height + "px";
|
||||
highlighter.style.width = targetRect.width + "px";
|
||||
// If the dimensions are within 40% of eachother, make the highlight a circle with the
|
||||
// largest dimension as the diameter.
|
||||
if (maxDimension / minDimension <= 1.4) {
|
||||
highlightHeight = highlightWidth = maxDimension;
|
||||
highlighter.style.borderRadius = "100%";
|
||||
} else {
|
||||
highlighter.style.borderRadius = "";
|
||||
}
|
||||
|
||||
highlighter.style.height = highlightHeight + "px";
|
||||
highlighter.style.width = highlightWidth + "px";
|
||||
|
||||
// Close a previous highlight so we can relocate the panel.
|
||||
if (highlighter.parentElement.state == "open") {
|
||||
|
@ -591,10 +611,12 @@ this.UITour = {
|
|||
let paddingTopPx = 0 - parseFloat(containerStyle.paddingTop);
|
||||
let paddingLeftPx = 0 - parseFloat(containerStyle.paddingLeft);
|
||||
let highlightStyle = highlightWindow.getComputedStyle(highlighter);
|
||||
let highlightHeightWithMin = Math.max(highlightHeight, parseFloat(highlightStyle.minHeight));
|
||||
let highlightWidthWithMin = Math.max(highlightWidth, parseFloat(highlightStyle.minWidth));
|
||||
let offsetX = paddingTopPx
|
||||
- (Math.max(0, parseFloat(highlightStyle.minWidth) - targetRect.width) / 2);
|
||||
- (Math.max(0, highlightWidthWithMin - targetRect.width) / 2);
|
||||
let offsetY = paddingLeftPx
|
||||
- (Math.max(0, parseFloat(highlightStyle.minHeight) - targetRect.height) / 2);
|
||||
- (Math.max(0, highlightHeightWithMin - targetRect.height) / 2);
|
||||
highlighter.parentElement.openPopup(aTargetEl, "overlap", offsetX, offsetY);
|
||||
}
|
||||
|
||||
|
@ -665,6 +687,7 @@ this.UITour = {
|
|||
let tooltipClose = document.getElementById("UITourTooltipClose");
|
||||
tooltipClose.addEventListener("command", this);
|
||||
|
||||
tooltip.setAttribute("targetName", aAnchor.targetName);
|
||||
tooltip.hidden = false;
|
||||
let alignment = "bottomcenter topright";
|
||||
tooltip.openPopup(aAnchorEl, alignment);
|
||||
|
@ -709,6 +732,7 @@ this.UITour = {
|
|||
|
||||
if (aMenuName == "appMenu") {
|
||||
aWindow.PanelUI.panel.setAttribute("noautohide", "true");
|
||||
aWindow.PanelUI.panel.addEventListener("popuphiding", this.onAppMenuHiding);
|
||||
if (aOpenCallback) {
|
||||
aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
|
||||
}
|
||||
|
@ -733,6 +757,31 @@ this.UITour = {
|
|||
}
|
||||
},
|
||||
|
||||
onAppMenuHiding: function(aEvent) {
|
||||
let win = aEvent.target.ownerDocument.defaultView;
|
||||
let annotationElements = new Map([
|
||||
// [annotationElement (panel), method to hide the annotation]
|
||||
[win.document.getElementById("UITourHighlightContainer"), UITour.hideHighlight.bind(UITour)],
|
||||
[win.document.getElementById("UITourTooltip"), UITour.hideInfo.bind(UITour)],
|
||||
]);
|
||||
annotationElements.forEach((hideMethod, annotationElement) => {
|
||||
if (annotationElement.state != "closed") {
|
||||
let targetName = annotationElement.getAttribute("targetName");
|
||||
UITour.getTarget(win, targetName).then((aTarget) => {
|
||||
// Since getTarget is async, we need to make sure that the target hasn't
|
||||
// changed since it may have just moved to somewhere outside of the app menu.
|
||||
if (annotationElement.getAttribute("targetName") != aTarget.targetName ||
|
||||
annotationElement.state == "closed" ||
|
||||
!UITour.targetIsInAppMenu(aTarget)) {
|
||||
return;
|
||||
}
|
||||
hideMethod(win);
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
});
|
||||
UITour.appMenuOpenForAnnotation.clear();
|
||||
},
|
||||
|
||||
startUrlbarCapture: function(aWindow, aExpectedText, aUrl) {
|
||||
let urlbar = aWindow.document.getElementById("urlbar");
|
||||
this.urlbarCapture.set(aWindow, {
|
||||
|
|
|
@ -10,6 +10,7 @@ support-files =
|
|||
skip-if = os == "linux" # Intermittent failures, bug 951965
|
||||
[browser_UITour2.js]
|
||||
[browser_UITour3.js]
|
||||
[browser_UITour_panel_close_annotation.js]
|
||||
[browser_UITour_sync.js]
|
||||
[browser_taskbar_preview.js]
|
||||
run-if = os == "win"
|
||||
|
|
|
@ -9,79 +9,13 @@ let gContentWindow;
|
|||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_untrusted_host(done) {
|
||||
loadTestPage(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
|
@ -92,7 +26,7 @@ let tests = [
|
|||
}, "http://mochi.test:8888/");
|
||||
},
|
||||
function test_unsecure_host(done) {
|
||||
loadTestPage(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let bookmarksMenu = document.getElementById("bookmarks-menu-button");
|
||||
ise(bookmarksMenu.open, false, "Bookmark menu should initially be closed");
|
||||
|
||||
|
@ -104,7 +38,7 @@ let tests = [
|
|||
},
|
||||
function test_unsecure_host_override(done) {
|
||||
Services.prefs.setBoolPref("browser.uitour.requireSecure", false);
|
||||
loadTestPage(function() {
|
||||
loadUITourTestPage(function() {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
|
@ -147,6 +81,27 @@ let tests = [
|
|||
gContentAPI.showHighlight("urlbar");
|
||||
waitForElementToBeVisible(highlight, test_highlight_2, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_circle(done) {
|
||||
function check_highlight_size() {
|
||||
let panel = highlight.parentElement;
|
||||
let anchor = panel.anchorNode;
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
info("addons target: width: " + anchorRect.width + " height: " + anchorRect.height);
|
||||
let maxDimension = Math.round(Math.max(anchorRect.width, anchorRect.height));
|
||||
let highlightRect = highlight.getBoundingClientRect();
|
||||
info("highlight: width: " + highlightRect.width + " height: " + highlightRect.height);
|
||||
is(Math.round(highlightRect.width), maxDimension, "The width of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), maxDimension, "The height of the highlight should be equal to the largest dimension of the target");
|
||||
is(Math.round(highlightRect.height), Math.round(highlightRect.width), "The height and width of the highlight should be the same to create a circle");
|
||||
is(highlight.style.borderRadius, "100%", "The border-radius should be 100% to create a circle");
|
||||
done();
|
||||
}
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
is_element_hidden(highlight, "Highlight should initially be hidden");
|
||||
|
||||
gContentAPI.showHighlight("addons");
|
||||
waitForElementToBeVisible(highlight, check_highlight_size, "Highlight should be shown after showHighlight()");
|
||||
},
|
||||
function test_highlight_customize_auto_open_close(done) {
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
gContentAPI.showHighlight("customize");
|
||||
|
|
|
@ -9,74 +9,8 @@ let gContentWindow;
|
|||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
|
|
@ -9,74 +9,8 @@ let gContentWindow;
|
|||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that annotations disappear when their target is hidden.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
let gTestTab;
|
||||
let gContentAPI;
|
||||
let gContentWindow;
|
||||
let highlight = document.getElementById("UITourHighlight");
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function test() {
|
||||
registerCleanupFunction(() => {
|
||||
// Close the find bar in case it's open in the remaining tab
|
||||
gBrowser.getFindBar(gBrowser.selectedTab).close();
|
||||
});
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
function test_highlight_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("urlbar", "test title", "test text");
|
||||
gContentAPI.showHighlight("customize");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the highlight outside which should close the app menu.
|
||||
gContentAPI.showHighlight("appMenu");
|
||||
waitForPopupAtAnchor(highlight.parentElement, document.getElementById("PanelUI-menu-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed after the highlight moved elsewhere.");
|
||||
is(tooltip.state, "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should move to the appMenu button and still be visible");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_hideMenu(done) {
|
||||
gContentAPI.showHighlight("customize");
|
||||
gContentAPI.showInfo("search", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Close the app menu and make sure the highlight also disappeared.
|
||||
gContentAPI.hideMenu("appMenu");
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel still should have closed");
|
||||
is(tooltip.state, "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("searchProvider", "test title", "test text");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
is(tooltip.state, "open", "The info panel should have remained open");
|
||||
done();
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_highlight_info_panel_click_find(done) {
|
||||
gContentAPI.showHighlight("help");
|
||||
gContentAPI.showInfo("customize", "customize me!", "awesome!");
|
||||
waitForElementToBeVisible(highlight, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Click the find button which should close the panel.
|
||||
let findButton = document.getElementById("find-button");
|
||||
EventUtils.synthesizeMouseAtCenter(findButton, {});
|
||||
waitForElementToBeHidden(highlight, function checkPanelIsClosed() {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Panel should have closed when the find bar opened");
|
||||
waitForElementToBeHidden(tooltip, function checkTooltipIsClosed() {
|
||||
isnot(tooltip.state, "open", "The info panel should have closed too");
|
||||
done();
|
||||
}, "Tooltip should hide with the menu");
|
||||
}, "Highlight should have disappeared when panel closed");
|
||||
}, "Highlight should be shown after showHighlight() for fixed panel items");
|
||||
},
|
||||
|
||||
function test_info_move_outside_panel(done) {
|
||||
gContentAPI.showInfo("addons", "test title", "test text");
|
||||
gContentAPI.showHighlight("urlbar");
|
||||
let addonsButton = document.getElementById("add-ons-button");
|
||||
waitForPopupAtAnchor(tooltip, addonsButton, function checkPanelIsOpen() {
|
||||
isnot(PanelUI.panel.state, "closed", "Panel should have opened");
|
||||
|
||||
// Move the info panel outside which should close the app menu.
|
||||
gContentAPI.showInfo("appMenu", "Cool menu button", "It's three lines");
|
||||
waitForPopupAtAnchor(tooltip, document.getElementById("PanelUI-menu-button"), () => {
|
||||
isnot(PanelUI.panel.state, "open",
|
||||
"Menu should have closed after the highlight moved elsewhere.");
|
||||
is(highlight.parentElement.state, "open", "The highlight should have remained visible");
|
||||
done();
|
||||
}, "Tooltip should move to the appMenu button and still be visible");
|
||||
}, "Tooltip should be shown after showInfo() for a panel item");
|
||||
},
|
||||
|
||||
];
|
|
@ -9,75 +9,11 @@ let gContentWindow;
|
|||
|
||||
Components.utils.import("resource:///modules/UITour.jsm");
|
||||
|
||||
function loadTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function test() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.prefs.clearUserPref("services.sync.username");
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
executeSoon(() => {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
UITourTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
|
|
|
@ -53,11 +53,20 @@ function waitForElementToBeVisible(element, nextTest, msg) {
|
|||
"Timeout waiting for visibility: " + msg);
|
||||
}
|
||||
|
||||
function waitForElementToBeHidden(element, nextTest, msg) {
|
||||
waitForCondition(() => is_hidden(element),
|
||||
() => {
|
||||
ok(true, msg);
|
||||
nextTest();
|
||||
},
|
||||
"Timeout waiting for invisibility: " + msg);
|
||||
}
|
||||
|
||||
function waitForPopupAtAnchor(popup, anchorNode, nextTest, msg) {
|
||||
waitForCondition(() => popup.popupBoxObject.anchorNode == anchorNode,
|
||||
() => {
|
||||
ok(true, msg);
|
||||
is_element_visible(popup);
|
||||
is_element_visible(popup, "Popup should be visible");
|
||||
nextTest();
|
||||
},
|
||||
"Timeout waiting for popup at anchor: " + msg);
|
||||
|
@ -67,3 +76,75 @@ function is_element_hidden(element, msg) {
|
|||
isnot(element, null, "Element should not be null, when checking visibility");
|
||||
ok(is_hidden(element), msg);
|
||||
}
|
||||
|
||||
function loadUITourTestPage(callback, host = "https://example.com/") {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
|
||||
let url = getRootDirectory(gTestPath) + "uitour.html";
|
||||
url = url.replace("chrome://mochitests/content/", host);
|
||||
|
||||
gTestTab = gBrowser.addTab(url);
|
||||
gBrowser.selectedTab = gTestTab;
|
||||
|
||||
gTestTab.linkedBrowser.addEventListener("load", function onLoad() {
|
||||
gTestTab.linkedBrowser.removeEventListener("load", onLoad);
|
||||
|
||||
gContentWindow = Components.utils.waiveXrays(gTestTab.linkedBrowser.contentDocument.defaultView);
|
||||
gContentAPI = gContentWindow.Mozilla.UITour;
|
||||
|
||||
waitForFocus(callback, gContentWindow);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function UITourTest() {
|
||||
Services.prefs.setBoolPref("browser.uitour.enabled", true);
|
||||
let testUri = Services.io.newURI("http://example.com", null, null);
|
||||
Services.perms.add(testUri, "uitour", Services.perms.ALLOW_ACTION);
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
delete window.UITour;
|
||||
delete window.gContentWindow;
|
||||
delete window.gContentAPI;
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
delete window.gTestTab;
|
||||
Services.prefs.clearUserPref("browser.uitour.enabled", true);
|
||||
Services.perms.remove("example.com", "uitour");
|
||||
});
|
||||
|
||||
function done() {
|
||||
executeSoon(() => {
|
||||
if (gTestTab)
|
||||
gBrowser.removeTab(gTestTab);
|
||||
gTestTab = null;
|
||||
|
||||
let highlight = document.getElementById("UITourHighlightContainer");
|
||||
is_element_hidden(highlight, "Highlight should be closed/hidden after UITour tab is closed");
|
||||
|
||||
let tooltip = document.getElementById("UITourTooltip");
|
||||
is_element_hidden(tooltip, "Tooltip should be closed/hidden after UITour tab is closed");
|
||||
|
||||
ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up");
|
||||
|
||||
is(UITour.pinnedTabs.get(window), null, "Any pinned tab should be closed after UITour tab is closed");
|
||||
|
||||
executeSoon(nextTest);
|
||||
});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
let test = tests.shift();
|
||||
info("Starting " + test.name);
|
||||
loadUITourTestPage(function() {
|
||||
test(done);
|
||||
});
|
||||
}
|
||||
nextTest();
|
||||
}
|
||||
|
|
|
@ -1532,10 +1532,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
|||
-moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-drag");
|
||||
}
|
||||
|
||||
#TabsToolbar:not(:-moz-lwtheme) > #tabbrowser-tabs > .tabbrowser-tab:not([selected]) {
|
||||
color: -moz-menubartext;
|
||||
}
|
||||
|
||||
.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label {
|
||||
outline: 1px dotted;
|
||||
}
|
||||
|
|
|
@ -2645,9 +2645,8 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
|||
border: none;
|
||||
}
|
||||
|
||||
.tabbrowser-tab:not(:-moz-lwtheme) {
|
||||
color: #333;
|
||||
text-shadow: @loweredShadow@;
|
||||
.tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.tabbrowser-tab[selected=true]:-moz-lwtheme {
|
||||
|
@ -2694,6 +2693,11 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
|||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
#TabsToolbar:not(:-moz-lwtheme) {
|
||||
color: #333;
|
||||
text-shadow: @loweredShadow@;
|
||||
}
|
||||
|
||||
/*
|
||||
* Draw the bottom border of the tabstrip when core doesn't do it for us:
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,19 @@
|
|||
|
||||
%include ../browser.inc
|
||||
|
||||
#PanelUI-button {
|
||||
background-image: -moz-linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0)),
|
||||
-moz-linear-gradient(hsla(210,54%,20%,0), hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, hsla(210,54%,20%,0)),
|
||||
-moz-linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0));
|
||||
background-size: 1px calc(100% - 1px), 1px calc(100% - 1px), 1px calc(100% - 1px) !important;
|
||||
background-position: 0px 0px, 1px 0px, 2px 0px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
#PanelUI-menu-button {
|
||||
margin: 0 7px 0 9px;
|
||||
}
|
||||
|
||||
.panel-subviews {
|
||||
padding: 4px;
|
||||
background-color: hsla(0,0%,100%,.97);
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
width: @tabCurveWidth@;
|
||||
}
|
||||
|
||||
.tabbrowser-tab:not([selected=true]),
|
||||
.tabbrowser-tab:-moz-lwtheme {
|
||||
color: inherit;
|
||||
}
|
||||
|
|
|
@ -1577,10 +1577,6 @@ toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
|
|||
-moz-image-region: rect(0, 64px, 16px, 48px) !important;
|
||||
}
|
||||
|
||||
#main-window[tabsintitlebar]:not([inFullscreen]) .tabbrowser-tab:not([selected]):not(:-moz-lwtheme) {
|
||||
color: CaptionText;
|
||||
}
|
||||
|
||||
/* tabbrowser-tab focus ring */
|
||||
.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label {
|
||||
outline: 1px dotted;
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "AudioCompactor.h"
|
||||
#if defined(MOZ_MEMORY)
|
||||
# include "mozmemory.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static size_t
|
||||
MallocGoodSize(size_t aSize)
|
||||
{
|
||||
# if defined(MOZ_MEMORY)
|
||||
return malloc_good_size(aSize);
|
||||
# else
|
||||
return aSize;
|
||||
# endif
|
||||
}
|
||||
|
||||
static size_t
|
||||
TooMuchSlop(size_t aSize, size_t aAllocSize, size_t aMaxSlop)
|
||||
{
|
||||
// If the allocated size is less then our target size, then we
|
||||
// are chunking. This means it will be completely filled with
|
||||
// zero slop.
|
||||
size_t slop = (aAllocSize > aSize) ? (aAllocSize - aSize) : 0;
|
||||
return slop > aMaxSlop;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AudioCompactor::GetChunkSamples(uint32_t aFrames, uint32_t aChannels,
|
||||
size_t aMaxSlop)
|
||||
{
|
||||
size_t size = AudioDataSize(aFrames, aChannels);
|
||||
size_t chunkSize = MallocGoodSize(size);
|
||||
|
||||
// Reduce the chunk size until we meet our slop goal or the chunk
|
||||
// approaches an unreasonably small size.
|
||||
while (chunkSize > 64 && TooMuchSlop(size, chunkSize, aMaxSlop)) {
|
||||
chunkSize = MallocGoodSize(chunkSize / 2);
|
||||
}
|
||||
|
||||
// Calculate the number of samples based on expected malloc size
|
||||
// in order to allow as many frames as possible to be packed.
|
||||
return chunkSize / sizeof(AudioDataValue);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
AudioCompactor::NativeCopy::operator()(AudioDataValue *aBuffer, size_t aSamples)
|
||||
{
|
||||
NS_ASSERTION(aBuffer, "cannot copy to null buffer pointer");
|
||||
NS_ASSERTION(aSamples, "cannot copy zero values");
|
||||
|
||||
size_t bufferBytes = aSamples * sizeof(AudioDataValue);
|
||||
size_t maxBytes = std::min(bufferBytes, mSourceBytes - mNextByte);
|
||||
uint32_t frames = maxBytes / BytesPerFrame(mChannels);
|
||||
size_t bytes = frames * BytesPerFrame(mChannels);
|
||||
|
||||
NS_ASSERTION((mNextByte + bytes) <= mSourceBytes,
|
||||
"tried to copy beyond source buffer");
|
||||
NS_ASSERTION(bytes <= bufferBytes, "tried to copy beyond destination buffer");
|
||||
|
||||
memcpy(aBuffer, mSource + mNextByte, bytes);
|
||||
|
||||
mNextByte += bytes;
|
||||
return frames;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,121 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#if !defined(AudioCompactor_h)
|
||||
#define AudioCompactor_h
|
||||
|
||||
#include "MediaQueue.h"
|
||||
#include "MediaData.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class AudioCompactor
|
||||
{
|
||||
public:
|
||||
AudioCompactor(MediaQueue<AudioData>& aQueue)
|
||||
: mQueue(aQueue)
|
||||
{ }
|
||||
|
||||
// Push audio data into the underlying queue with minimal heap allocation
|
||||
// slop. This method is responsible for allocating AudioDataValue[] buffers.
|
||||
// The caller must provide a functor to copy the data into the buffers. The
|
||||
// functor must provide the following signature:
|
||||
//
|
||||
// uint32_t operator()(AudioDataValue *aBuffer, size_t aSamples);
|
||||
//
|
||||
// The functor must copy as many complete frames as possible to the provided
|
||||
// buffer given its length (in AudioDataValue elements). The number of frames
|
||||
// copied must be returned. This copy functor must support being called
|
||||
// multiple times in order to copy the audio data fully. The copy functor
|
||||
// must copy full frames as partial frames will be ignored.
|
||||
template<typename CopyFunc>
|
||||
bool Push(int64_t aOffset, int64_t aTime, int32_t aSampleRate,
|
||||
uint32_t aFrames, uint32_t aChannels, CopyFunc aCopyFunc)
|
||||
{
|
||||
// If we are losing more than a reasonable amount to padding, try to chunk
|
||||
// the data.
|
||||
size_t maxSlop = AudioDataSize(aFrames, aChannels) / MAX_SLOP_DIVISOR;
|
||||
|
||||
while (aFrames > 0) {
|
||||
uint32_t samples = GetChunkSamples(aFrames, aChannels, maxSlop);
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[samples]);
|
||||
|
||||
// Copy audio data to buffer using caller-provided functor.
|
||||
uint32_t framesCopied = aCopyFunc(buffer, samples);
|
||||
|
||||
NS_ASSERTION(framesCopied <= aFrames, "functor copied too many frames");
|
||||
|
||||
CheckedInt64 duration = FramesToUsecs(framesCopied, aSampleRate);
|
||||
if (!duration.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mQueue.Push(new AudioData(aOffset,
|
||||
aTime,
|
||||
duration.value(),
|
||||
framesCopied,
|
||||
buffer.forget(),
|
||||
aChannels));
|
||||
|
||||
// Remove the frames we just pushed into the queue and loop if there is
|
||||
// more to be done.
|
||||
aTime += duration.value();
|
||||
aFrames -= framesCopied;
|
||||
|
||||
// NOTE: No need to update aOffset as its only an approximation anyway.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy functor suitable for copying audio samples already in the
|
||||
// AudioDataValue format/layout expected by AudioStream on this platform.
|
||||
class NativeCopy
|
||||
{
|
||||
public:
|
||||
NativeCopy(const uint8_t* aSource, size_t aSourceBytes,
|
||||
uint32_t aChannels)
|
||||
: mSource(aSource)
|
||||
, mSourceBytes(aSourceBytes)
|
||||
, mChannels(aChannels)
|
||||
, mNextByte(0)
|
||||
{ }
|
||||
|
||||
uint32_t operator()(AudioDataValue *aBuffer, size_t aSamples);
|
||||
|
||||
private:
|
||||
const uint8_t* const mSource;
|
||||
const size_t mSourceBytes;
|
||||
const uint32_t mChannels;
|
||||
size_t mNextByte;
|
||||
};
|
||||
|
||||
// Allow 12.5% slop before chunking kicks in. Public so that the gtest can
|
||||
// access it.
|
||||
static const size_t MAX_SLOP_DIVISOR = 8;
|
||||
|
||||
private:
|
||||
// Compute the number of AudioDataValue samples that will be fit the most
|
||||
// frames while keeping heap allocation slop less than the given threshold.
|
||||
static uint32_t
|
||||
GetChunkSamples(uint32_t aFrames, uint32_t aChannels, size_t aMaxSlop);
|
||||
|
||||
static size_t BytesPerFrame(uint32_t aChannels)
|
||||
{
|
||||
return sizeof(AudioDataValue) * aChannels;
|
||||
}
|
||||
|
||||
static size_t AudioDataSize(uint32_t aFrames, uint32_t aChannels)
|
||||
{
|
||||
return aFrames * BytesPerFrame(aChannels);
|
||||
}
|
||||
|
||||
MediaQueue<AudioData> &mQueue;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // AudioCompactor_h
|
|
@ -45,8 +45,7 @@ void* MediaDecoderReader::VideoQueueMemoryFunctor::operator()(void* anObject) {
|
|||
}
|
||||
|
||||
MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
|
||||
: mAudioCompactor(mAudioQueue),
|
||||
mDecoder(aDecoder),
|
||||
: mDecoder(aDecoder),
|
||||
mIgnoreAudioOutputFormat(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(MediaDecoderReader);
|
||||
|
@ -281,3 +280,4 @@ MediaDecoderReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered,
|
|||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include "MediaInfo.h"
|
||||
#include "MediaData.h"
|
||||
#include "MediaQueue.h"
|
||||
#include "AudioCompactor.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -106,12 +105,6 @@ protected:
|
|||
// the decoder, state machine, and main threads.
|
||||
MediaQueue<VideoData> mVideoQueue;
|
||||
|
||||
// An adapter to the audio queue which first copies data to buffers with
|
||||
// minimal allocation slop and then pushes them to the queue. This is
|
||||
// useful for decoders working with formats that give awkward numbers of
|
||||
// frames such as mp3.
|
||||
AudioCompactor mAudioCompactor;
|
||||
|
||||
public:
|
||||
// Populates aBuffered with the time ranges which are buffered. aStartTime
|
||||
// must be the presentation time of the first frame in the media, e.g.
|
||||
|
|
|
@ -45,13 +45,6 @@ extern PRLogModuleInfo* gMediaDecoderLog;
|
|||
#define DECODER_LOG(type, msg)
|
||||
#endif
|
||||
|
||||
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
|
||||
// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
|
||||
// implementation. With unified builds, putting this in headers is not enough.
|
||||
#ifdef GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
#endif
|
||||
|
||||
// Wait this number of seconds when buffering, then leave and play
|
||||
// as best as we can if the required amount of data hasn't been
|
||||
// retrieved.
|
||||
|
|
|
@ -92,13 +92,6 @@ namespace mozilla {
|
|||
class AudioSegment;
|
||||
class VideoSegment;
|
||||
|
||||
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
|
||||
// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
|
||||
// implementation.
|
||||
#ifdef GetCurrentTime
|
||||
#undef GetCurrentTime
|
||||
#endif
|
||||
|
||||
/*
|
||||
The state machine class. This manages the decoding and seeking in the
|
||||
MediaDecoderReader on the decode thread, and A/V sync on the shared
|
||||
|
|
|
@ -204,7 +204,7 @@ public:
|
|||
void Revoke() { mResource = nullptr; }
|
||||
|
||||
private:
|
||||
nsRefPtr<RtspMediaResource> mResource;
|
||||
RtspMediaResource* mResource;
|
||||
};
|
||||
friend class Listener;
|
||||
|
||||
|
|
|
@ -12,12 +12,8 @@
|
|||
#define AUDIO_READ_BYTES 4096
|
||||
|
||||
// Maximum number of audio frames we will accept from the audio decoder in one
|
||||
// go. Carefully select this to work well with both the mp3 1152 max frames
|
||||
// per block and power-of-2 allocation sizes. Since we must pre-allocate the
|
||||
// buffer we cannot use AudioCompactor without paying for an additional
|
||||
// allocation and copy. Therefore, choosing a value that divides exactly into
|
||||
// 1152 is most memory efficient.
|
||||
#define MAX_AUDIO_FRAMES 128
|
||||
// go.
|
||||
#define MAX_AUDIO_FRAMES 4096
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -205,8 +201,7 @@ AppleMP3Reader::AudioSampleCallback(UInt32 aNumBytes,
|
|||
LOGD("got %u bytes, %u packets\n", aNumBytes, aNumPackets);
|
||||
|
||||
// 1 frame per packet * num channels * 32-bit float
|
||||
uint32_t decodedSize = MAX_AUDIO_FRAMES * mAudioChannels *
|
||||
sizeof(AudioDataValue);
|
||||
uint32_t decodedSize = MAX_AUDIO_FRAMES * mAudioChannels * 4;
|
||||
|
||||
// descriptions for _decompressed_ audio packets. ignored.
|
||||
nsAutoArrayPtr<AudioStreamPacketDescription>
|
||||
|
@ -243,14 +238,6 @@ AppleMP3Reader::AudioSampleCallback(UInt32 aNumBytes,
|
|||
break;
|
||||
}
|
||||
|
||||
// If we decoded zero frames then AudiOConverterFillComplexBuffer is out
|
||||
// of data to provide. We drained its internal buffer completely on the
|
||||
// last pass.
|
||||
if (numFrames == 0 && rv == kNeedMoreData) {
|
||||
LOGD("FillComplexBuffer out of data exactly\n");
|
||||
break;
|
||||
}
|
||||
|
||||
int64_t time = FramesToUsecs(mCurrentAudioFrame, mAudioSampleRate).value();
|
||||
int64_t duration = FramesToUsecs(numFrames, mAudioSampleRate).value();
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
@ -248,48 +247,6 @@ DirectShowReader::Finish(HRESULT aStatus)
|
|||
return false;
|
||||
}
|
||||
|
||||
class DirectShowCopy
|
||||
{
|
||||
public:
|
||||
DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample,
|
||||
uint32_t aSamples, uint32_t aChannels)
|
||||
: mSource(aSource)
|
||||
, mBytesPerSample(aBytesPerSample)
|
||||
, mSamples(aSamples)
|
||||
, mChannels(aChannels)
|
||||
, mNextSample(0)
|
||||
{ }
|
||||
|
||||
uint32_t operator()(AudioDataValue *aBuffer, size_t aSamples)
|
||||
{
|
||||
size_t maxSamples = std::min(aSamples, mSamples - mNextSample);
|
||||
uint32_t frames = maxSamples / mChannels;
|
||||
size_t byteOffset = mNextSample * mBytesPerSample;
|
||||
if (mBytesPerSample == 1) {
|
||||
for (uint32_t i = 0; i < maxSamples; ++i) {
|
||||
uint8_t *sample = mSource + byteOffset;
|
||||
aBuffer[i] = UnsignedByteToAudioSample(*sample);
|
||||
byteOffset += mBytesPerSample;
|
||||
}
|
||||
} else if (mBytesPerSample == 2) {
|
||||
for (uint32_t i = 0; i < maxSamples; ++i) {
|
||||
int16_t *sample = reinterpret_cast<int16_t *>(mSource + byteOffset);
|
||||
aBuffer[i] = AudioSampleToFloat(*sample);
|
||||
byteOffset += mBytesPerSample;
|
||||
}
|
||||
}
|
||||
mNextSample = maxSamples;
|
||||
return frames;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t * const mSource;
|
||||
const uint32_t mBytesPerSample;
|
||||
const uint32_t mSamples;
|
||||
const uint32_t mChannels;
|
||||
uint32_t mNextSample;
|
||||
};
|
||||
|
||||
bool
|
||||
DirectShowReader::DecodeAudioData()
|
||||
{
|
||||
|
@ -324,15 +281,26 @@ DirectShowReader::DecodeAudioData()
|
|||
hr = sample->GetPointer(&data);
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr));
|
||||
|
||||
mAudioCompactor.Push(mDecoder->GetResource()->Tell(),
|
||||
RefTimeToUsecs(start),
|
||||
mInfo.mAudio.mRate,
|
||||
numFrames,
|
||||
mNumChannels,
|
||||
DirectShowCopy(reinterpret_cast<uint8_t *>(data),
|
||||
mBytesPerSample,
|
||||
numSamples,
|
||||
mNumChannels));
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[numSamples]);
|
||||
AudioDataValue* dst = buffer.get();
|
||||
if (mBytesPerSample == 1) {
|
||||
uint8_t* src = reinterpret_cast<uint8_t*>(data);
|
||||
for (int32_t i = 0; i < numSamples; ++i) {
|
||||
dst[i] = UnsignedByteToAudioSample(src[i]);
|
||||
}
|
||||
} else if (mBytesPerSample == 2) {
|
||||
int16_t* src = reinterpret_cast<int16_t*>(data);
|
||||
for (int32_t i = 0; i < numSamples; ++i) {
|
||||
dst[i] = AudioSampleToFloat(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
|
||||
RefTimeToUsecs(start),
|
||||
RefTimeToUsecs(end - start),
|
||||
numFrames,
|
||||
buffer.forget(),
|
||||
mNumChannels));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -532,20 +532,20 @@ bool GStreamerReader::DecodeAudioData()
|
|||
timestamp = gst_segment_to_stream_time(&mAudioSegment,
|
||||
GST_FORMAT_TIME, timestamp);
|
||||
timestamp = GST_TIME_AS_USECONDS(timestamp);
|
||||
int64_t duration = 0;
|
||||
if (GST_CLOCK_TIME_IS_VALID(GST_BUFFER_DURATION(buffer)))
|
||||
duration = GST_TIME_AS_USECONDS(GST_BUFFER_DURATION(buffer));
|
||||
|
||||
int64_t offset = GST_BUFFER_OFFSET(buffer);
|
||||
unsigned int size = GST_BUFFER_SIZE(buffer);
|
||||
int32_t frames = (size / sizeof(AudioDataValue)) / mInfo.mAudio.mChannels;
|
||||
ssize_t outSize = static_cast<size_t>(size / sizeof(AudioDataValue));
|
||||
nsAutoArrayPtr<AudioDataValue> data(new AudioDataValue[outSize]);
|
||||
memcpy(data, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer));
|
||||
AudioData* audio = new AudioData(offset, timestamp, duration,
|
||||
frames, data.forget(), mInfo.mAudio.mChannels);
|
||||
|
||||
typedef AudioCompactor::NativeCopy GstCopy;
|
||||
mAudioCompactor.Push(offset,
|
||||
timestamp,
|
||||
mInfo.mAudio.mRate,
|
||||
frames,
|
||||
mInfo.mAudio.mChannels,
|
||||
GstCopy(GST_BUFFER_DATA(buffer),
|
||||
size,
|
||||
mInfo.mAudio.mChannels));
|
||||
mAudioQueue.Push(audio);
|
||||
gst_buffer_unref(buffer);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gtest/gtest.h"
|
||||
#include "AudioCompactor.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
|
||||
using mozilla::AudioCompactor;
|
||||
using mozilla::AudioData;
|
||||
using mozilla::AudioDataValue;
|
||||
using mozilla::MediaDecoderReader;
|
||||
using mozilla::MediaQueue;
|
||||
|
||||
class TestCopy
|
||||
{
|
||||
public:
|
||||
TestCopy(uint32_t aFrames, uint32_t aChannels,
|
||||
uint32_t &aCallCount, uint32_t &aFrameCount)
|
||||
: mFrames(aFrames)
|
||||
, mChannels(aChannels)
|
||||
, mCallCount(aCallCount)
|
||||
, mFrameCount(aFrameCount)
|
||||
{ }
|
||||
|
||||
uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples)
|
||||
{
|
||||
mCallCount += 1;
|
||||
uint32_t frames = std::min(mFrames - mFrameCount, aSamples / mChannels);
|
||||
mFrameCount += frames;
|
||||
return frames;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t mFrames;
|
||||
const uint32_t mChannels;
|
||||
uint32_t &mCallCount;
|
||||
uint32_t &mFrameCount;
|
||||
};
|
||||
|
||||
static void TestAudioCompactor(size_t aBytes)
|
||||
{
|
||||
MediaQueue<AudioData> queue;
|
||||
AudioCompactor compactor(queue);
|
||||
|
||||
uint64_t offset = 0;
|
||||
uint64_t time = 0;
|
||||
uint32_t sampleRate = 44000;
|
||||
uint32_t channels = 2;
|
||||
uint32_t frames = aBytes / (channels * sizeof(AudioDataValue));
|
||||
size_t maxSlop = aBytes / AudioCompactor::MAX_SLOP_DIVISOR;
|
||||
|
||||
uint32_t callCount = 0;
|
||||
uint32_t frameCount = 0;
|
||||
|
||||
compactor.Push(offset, time, sampleRate, frames, channels,
|
||||
TestCopy(frames, channels, callCount, frameCount));
|
||||
|
||||
EXPECT_GT(callCount, 0U) << "copy functor never called";
|
||||
EXPECT_EQ(frames, frameCount) << "incorrect number of frames copied";
|
||||
|
||||
MediaDecoderReader::AudioQueueMemoryFunctor memoryFunc;
|
||||
queue.LockedForEach(memoryFunc);
|
||||
size_t allocSize = memoryFunc.mSize - (callCount * sizeof(AudioData));
|
||||
size_t slop = allocSize - aBytes;
|
||||
EXPECT_LE(slop, maxSlop) << "allowed too much allocation slop";
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_4000)
|
||||
{
|
||||
TestAudioCompactor(4000);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_4096)
|
||||
{
|
||||
TestAudioCompactor(4096);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_5000)
|
||||
{
|
||||
TestAudioCompactor(5000);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_5256)
|
||||
{
|
||||
TestAudioCompactor(5256);
|
||||
}
|
||||
|
||||
TEST(Media, AudioCompactor_NativeCopy)
|
||||
{
|
||||
const uint32_t channels = 2;
|
||||
const size_t srcBytes = 32;
|
||||
const uint32_t srcSamples = srcBytes / sizeof(AudioDataValue);
|
||||
const uint32_t srcFrames = srcSamples / channels;
|
||||
uint8_t src[srcBytes];
|
||||
|
||||
for (uint32_t i = 0; i < srcBytes; ++i) {
|
||||
src[i] = i;
|
||||
}
|
||||
|
||||
AudioCompactor::NativeCopy copy(src, srcBytes, channels);
|
||||
|
||||
const uint32_t dstSamples = srcSamples * 2;
|
||||
AudioDataValue dst[dstSamples];
|
||||
|
||||
const AudioDataValue notCopied = 0xffff;
|
||||
for (uint32_t i = 0; i < dstSamples; ++i) {
|
||||
dst[i] = notCopied;
|
||||
}
|
||||
|
||||
const uint32_t copyCount = 8;
|
||||
uint32_t copiedFrames = 0;
|
||||
uint32_t nextSample = 0;
|
||||
for (uint32_t i = 0; i < copyCount; ++i) {
|
||||
uint32_t copySamples = dstSamples / copyCount;
|
||||
copiedFrames += copy(dst + nextSample, copySamples);
|
||||
nextSample += copySamples;
|
||||
}
|
||||
|
||||
EXPECT_EQ(srcFrames, copiedFrames) << "copy exact number of source frames";
|
||||
|
||||
// Verify that the only the correct bytes were copied.
|
||||
for (uint32_t i = 0; i < dstSamples; ++i) {
|
||||
if (i < srcSamples) {
|
||||
EXPECT_NE(notCopied, dst[i]) << "should have copied over these bytes";
|
||||
} else {
|
||||
EXPECT_EQ(notCopied, dst[i]) << "should not have copied over these bytes";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
LIBRARY_NAME = 'media_gtest'
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestAudioCompactor.cpp',
|
||||
'TestTrackEncoder.cpp',
|
||||
]
|
||||
|
||||
|
|
|
@ -58,7 +58,6 @@ EXPORTS += [
|
|||
'AbstractMediaDecoder.h',
|
||||
'AudioAvailableEventManager.h',
|
||||
'AudioChannelFormat.h',
|
||||
'AudioCompactor.h',
|
||||
'AudioEventTimeline.h',
|
||||
'AudioNodeEngine.h',
|
||||
'AudioNodeExternalInputStream.h',
|
||||
|
@ -116,7 +115,6 @@ EXPORTS.mozilla.dom += [
|
|||
UNIFIED_SOURCES += [
|
||||
'AudioAvailableEventManager.cpp',
|
||||
'AudioChannelFormat.cpp',
|
||||
'AudioCompactor.cpp',
|
||||
'AudioNodeEngine.cpp',
|
||||
'AudioNodeExternalInputStream.cpp',
|
||||
'AudioNodeStream.cpp',
|
||||
|
|
|
@ -312,29 +312,33 @@ bool MediaOmxReader::DecodeAudioData()
|
|||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
|
||||
// Read next frame
|
||||
MPAPI::AudioFrame source;
|
||||
if (!mOmxDecoder->ReadAudio(&source, mAudioSeekTimeUs)) {
|
||||
MPAPI::AudioFrame frame;
|
||||
if (!mOmxDecoder->ReadAudio(&frame, mAudioSeekTimeUs)) {
|
||||
return false;
|
||||
}
|
||||
mAudioSeekTimeUs = -1;
|
||||
|
||||
// Ignore empty buffer which stagefright media read will sporadically return
|
||||
if (source.mSize == 0) {
|
||||
if (frame.mSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t frames = source.mSize / (source.mAudioChannels *
|
||||
sizeof(AudioDataValue));
|
||||
nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frame.mSize/2] );
|
||||
memcpy(buffer.get(), frame.mData, frame.mSize);
|
||||
|
||||
typedef AudioCompactor::NativeCopy OmxCopy;
|
||||
return mAudioCompactor.Push(pos,
|
||||
source.mTimeUs,
|
||||
source.mAudioSampleRate,
|
||||
frames,
|
||||
source.mAudioChannels,
|
||||
OmxCopy(static_cast<uint8_t *>(source.mData),
|
||||
source.mSize,
|
||||
source.mAudioChannels));
|
||||
uint32_t frames = frame.mSize / (2 * frame.mAudioChannels);
|
||||
CheckedInt64 duration = FramesToUsecs(frames, frame.mAudioSampleRate);
|
||||
if (!duration.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mAudioQueue.Push(new AudioData(pos,
|
||||
frame.mTimeUs,
|
||||
duration.value(),
|
||||
frames,
|
||||
buffer.forget(),
|
||||
frame.mAudioChannels));
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
|
||||
|
|
|
@ -1467,7 +1467,7 @@ BluetoothOppManager::OnSocketConnectError(BluetoothSocket* aSocket)
|
|||
}
|
||||
|
||||
// Listen as a server if there's no more batch to process
|
||||
if (!ProcessNextBatch()) {
|
||||
if (!ProcessNextBatch() && !mIsServer) {
|
||||
Listen();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=944397
|
|||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944397">Mozilla Bug 944397</a>
|
||||
<input type="text" />
|
||||
<p id="display"></p>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="application/javascript;version=1.7">
|
||||
|
@ -26,6 +25,14 @@ function appFrameScript() {
|
|||
input.oninput = function() {
|
||||
sendAsyncMessage('test:InputMethod:oninput', {});
|
||||
};
|
||||
|
||||
/*
|
||||
* Bug 957213. Sometimes we need to refocus the input field to avoid
|
||||
* intermittent test failure.
|
||||
*/
|
||||
content.setInterval(function() {
|
||||
input.focus();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
|
@ -40,8 +47,12 @@ function runTest() {
|
|||
let mm = SpecialPowers.getBrowserFrameMessageManager(app);
|
||||
mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
|
||||
mm.addMessageListener("test:InputMethod:oninput", function() {
|
||||
if (!timeoutId) {
|
||||
return;
|
||||
}
|
||||
ok(true, 'Keyboard input was received.');
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
inputmethod_cleanup();
|
||||
});
|
||||
});
|
||||
|
@ -80,7 +91,7 @@ function runTest() {
|
|||
timeoutId = setTimeout(function() {
|
||||
inputmethod_cleanup();
|
||||
ok(false, 'Failed to generate keyboard input.');
|
||||
}, 60000);
|
||||
}, 20000);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
|
@ -89,6 +100,7 @@ function runTest() {
|
|||
};
|
||||
|
||||
// Loads the input method app to the browser frame after a delay.
|
||||
SpecialPowers.DOMWindowUtils.focus(app);
|
||||
setTimeout(function() {
|
||||
keyboard.src = imeUrl;
|
||||
}, 100);
|
||||
|
|
|
@ -610,7 +610,7 @@ TabChild::HandlePossibleViewportChange()
|
|||
// The page must have been refreshed in some way such as a new document or
|
||||
// new CSS viewport, so we know that there's no velocity, acceleration, and
|
||||
// we have no idea how long painting will take.
|
||||
metrics, ScreenPoint(0.0f, 0.0f), gfx::Point(0.0f, 0.0f), 0.0);
|
||||
metrics, ScreenPoint(0.0f, 0.0f), 0.0);
|
||||
metrics.mCumulativeResolution = metrics.mZoom / metrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
|
||||
// This is the root layer, so the cumulative resolution is the same
|
||||
// as the resolution.
|
||||
|
|
|
@ -29,6 +29,15 @@ const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
|
|||
const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
|
||||
const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
|
||||
|
||||
// Networks have different status that NetworkStats API needs to be aware of.
|
||||
// Network is present and ready, so NetworkManager provides the whole info.
|
||||
const NETWORK_STATUS_READY = 0;
|
||||
// Network is present but hasn't established a connection yet (e.g. SIM that has not
|
||||
// enabled 3G since boot).
|
||||
const NETWORK_STATUS_STANDBY = 1;
|
||||
// Network is not present, but stored in database by the previous connections.
|
||||
const NETWORK_STATUS_AWAY = 2;
|
||||
|
||||
// The maximum traffic amount can be saved in the |cachedStats|.
|
||||
const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
|
||||
|
||||
|
@ -89,7 +98,8 @@ this.NetworkStatsService = {
|
|||
let netId = this.getNetworkId('0', NET_TYPE_WIFI);
|
||||
this._networks[netId] = { network: { id: '0',
|
||||
type: NET_TYPE_WIFI },
|
||||
interfaceName: null };
|
||||
interfaceName: null,
|
||||
status: NETWORK_STATUS_STANDBY };
|
||||
|
||||
this.messages = ["NetworkStats:Get",
|
||||
"NetworkStats:Clear",
|
||||
|
@ -278,6 +288,7 @@ this.NetworkStatsService = {
|
|||
type: aNetwork.type };
|
||||
}
|
||||
|
||||
this._networks[netId].status = NETWORK_STATUS_READY;
|
||||
this._networks[netId].interfaceName = aNetwork.name;
|
||||
return netId;
|
||||
},
|
||||
|
@ -286,6 +297,46 @@ this.NetworkStatsService = {
|
|||
return aIccId + '' + aNetworkType;
|
||||
},
|
||||
|
||||
/* Function to ensure that one network is valid. The network is valid if its status is
|
||||
* NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY.
|
||||
*
|
||||
* The result is |netId| or null in case of a non-valid network
|
||||
* aCallback is signatured as |function(netId)|.
|
||||
*/
|
||||
validateNetwork: function validateNetwork(aNetwork, aCallback) {
|
||||
let netId = this.getNetworkId(aNetwork.id, aNetwork.type);
|
||||
|
||||
if (this._networks[netId]) {
|
||||
aCallback(netId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if network is valid (RIL entry) but has not established a connection yet.
|
||||
// If so add to networks list with empty interfaceName.
|
||||
let rilNetworks = this.getRilNetworks();
|
||||
if (rilNetworks[netId]) {
|
||||
this._networks[netId] = Object.create(null);
|
||||
this._networks[netId].network = rilNetworks[netId];
|
||||
this._networks[netId].status = NETWORK_STATUS_STANDBY;
|
||||
this._currentAlarms[netId] = Object.create(null);
|
||||
aCallback(netId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if network is available in the DB.
|
||||
this._db.isNetworkAvailable(aNetwork, function(aError, aResult) {
|
||||
if (aResult) {
|
||||
this._networks[netId] = Object.create(null);
|
||||
this._networks[netId].network = aNetwork;
|
||||
this._networks[netId].status = NETWORK_STATUS_AWAY;
|
||||
this._currentAlarms[netId] = Object.create(null);
|
||||
aCallback(netId);
|
||||
}
|
||||
|
||||
aCallback(null);
|
||||
});
|
||||
},
|
||||
|
||||
getAvailableNetworks: function getAvailableNetworks(mm, msg) {
|
||||
let self = this;
|
||||
let rilNetworks = this.getRilNetworks();
|
||||
|
@ -368,91 +419,55 @@ this.NetworkStatsService = {
|
|||
let start = new Date(msg.start);
|
||||
let end = new Date(msg.end);
|
||||
|
||||
// Check if the network is currently active. If yes, we need to update
|
||||
// the cached stats first before retrieving stats from the DB.
|
||||
if (this._networks[netId]) {
|
||||
this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
|
||||
debug("getstats for network " + network.id + " of type " + network.type);
|
||||
debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
|
||||
|
||||
self.updateCachedStats(function onStatsUpdated(aResult, aMessage) {
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, appId, serviceType, network, start, end, appManifestURL);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the network is available in the DB. If yes, we also
|
||||
// retrieve the stats for it from the DB.
|
||||
this._db.isNetworkAvailable(network, function(aError, aResult) {
|
||||
let toFind = false;
|
||||
if (aResult) {
|
||||
toFind = true;
|
||||
} else if (!aError) {
|
||||
// Network is not found in the database without any errors.
|
||||
// Check if network is valid but has not established a connection yet.
|
||||
let rilNetworks = self.getRilNetworks();
|
||||
if (rilNetworks[netId]) {
|
||||
// find will not get data for network from the database but will format the
|
||||
// result object in order to make NetworkStatsManager be able to construct a
|
||||
// nsIDOMMozNetworkStats object.
|
||||
toFind = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (toFind) {
|
||||
// If network is not active, there is no need to update stats before finding.
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, appId, serviceType, network, start, end, appManifestURL);
|
||||
this.validateNetwork(network, function onValidateNetwork(aNetId) {
|
||||
if (!aNetId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid connectionType", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aError) {
|
||||
aError = "Invalid connectionType";
|
||||
// If network is currently active we need to update the cached stats first before
|
||||
// retrieving stats from the DB.
|
||||
if (self._networks[aNetId].status == NETWORK_STATUS_READY) {
|
||||
self.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) {
|
||||
debug("getstats for network " + network.id + " of type " + network.type);
|
||||
debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
|
||||
|
||||
self.updateCachedStats(function onStatsUpdated(aResult, aMessage) {
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, appId, serviceType, network, start, end, appManifestURL);
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: null });
|
||||
// Network not active, so no need to update
|
||||
self._db.find(function onStatsFound(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
}, appId, serviceType, network, start, end, appManifestURL);
|
||||
});
|
||||
},
|
||||
|
||||
clearInterfaceStats: function clearInterfaceStats(mm, msg) {
|
||||
let self = this;
|
||||
let network = msg.network;
|
||||
let netId = this.getNetworkId(network.id, network.type);
|
||||
|
||||
debug("clear stats for network " + network.id + " of type " + network.type);
|
||||
|
||||
if (!this._networks[netId]) {
|
||||
// Check if network is valid but has not established a connection yet. If it is not
|
||||
// found in RIL networks, it can be a SIM network used in the past having sample
|
||||
// in the database.
|
||||
let rilNetworks = this.getRilNetworks();
|
||||
if (!rilNetworks[netId]) {
|
||||
// Check if it is available in the DB.
|
||||
this._db.isNetworkAvailable(network, function(aError, aResult) {
|
||||
if (aResult) {
|
||||
this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: "Invalid networkType", result: null });
|
||||
});
|
||||
this.validateNetwork(network, function onValidateNetwork(aNetId) {
|
||||
if (!aNetId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: "Invalid connectionType", result: null });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: aError, result: aResult });
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -478,7 +493,7 @@ this.NetworkStatsService = {
|
|||
this.updateCachedStats();
|
||||
|
||||
let elements = [];
|
||||
let lastElement;
|
||||
let lastElement = null;
|
||||
|
||||
// For each connectionType create an object containning the type
|
||||
// and the 'queueIndex', the 'queueIndex' is an integer representing
|
||||
|
@ -487,14 +502,27 @@ this.NetworkStatsService = {
|
|||
// else it is pushed in 'elements' array, which later will be pushed to
|
||||
// the queue array.
|
||||
for (let netId in this._networks) {
|
||||
if (this._networks[netId].status != NETWORK_STATUS_READY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lastElement = { netId: netId,
|
||||
queueIndex: this.updateQueueIndex(netId)};
|
||||
|
||||
if (lastElement.queueIndex == -1) {
|
||||
elements.push({netId: lastElement.netId, callbacks: []});
|
||||
elements.push({ netId: lastElement.netId, callbacks: [] });
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastElement) {
|
||||
// No elements need to be updated, probably because status is different than
|
||||
// NETWORK_STATUS_READY.
|
||||
if (aCallback) {
|
||||
aCallback(true, "OK");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (elements.length > 0) {
|
||||
// If length of elements is greater than 0, callback is set to
|
||||
// the last element.
|
||||
|
@ -800,21 +828,29 @@ this.NetworkStatsService = {
|
|||
},
|
||||
|
||||
getAlarms: function getAlarms(mm, msg) {
|
||||
let self = this;
|
||||
let network = msg.data.network;
|
||||
let manifestURL = msg.data.manifestURL;
|
||||
|
||||
let netId = null;
|
||||
if (network) {
|
||||
netId = this.getNetworkId(network.id, network.type);
|
||||
if (!this._networks[netId]) {
|
||||
mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
|
||||
{ id: msg.id, error: "InvalidInterface", result: null });
|
||||
return;
|
||||
}
|
||||
this.validateNetwork(network, function onValidateNetwork(aNetId) {
|
||||
if (!aNetId) {
|
||||
mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
|
||||
{ id: msg.id, error: "InvalidInterface", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
self._getAlarms(mm, msg, aNetId, manifestURL);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._getAlarms(mm, msg, null, manifestURL);
|
||||
},
|
||||
|
||||
_getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) {
|
||||
let self = this;
|
||||
this._db.getAlarms(netId, manifestURL, function onCompleted(error, result) {
|
||||
this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) {
|
||||
if (error) {
|
||||
mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
|
||||
{ id: msg.id, error: error, result: result });
|
||||
|
@ -884,48 +920,49 @@ this.NetworkStatsService = {
|
|||
return;
|
||||
}
|
||||
|
||||
let netId = this.getNetworkId(network.id, network.type);
|
||||
if (!this._networks[netId]) {
|
||||
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
|
||||
{ id: msg.id, error: "InvalidiConnectionType", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
let newAlarm = {
|
||||
id: null,
|
||||
networkId: netId,
|
||||
threshold: threshold,
|
||||
absoluteThreshold: null,
|
||||
startTime: options.startTime,
|
||||
data: options.data,
|
||||
pageURL: options.pageURL,
|
||||
manifestURL: options.manifestURL
|
||||
};
|
||||
|
||||
let self = this;
|
||||
this._updateThreshold(newAlarm, function onUpdate(error, _threshold) {
|
||||
if (error) {
|
||||
this.validateNetwork(network, function onValidateNetwork(aNetId) {
|
||||
if (!aNetId) {
|
||||
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
|
||||
{ id: msg.id, error: error, result: null });
|
||||
{ id: msg.id, error: "InvalidiConnectionType", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
newAlarm.absoluteThreshold = _threshold.absoluteThreshold;
|
||||
self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
|
||||
let newAlarm = {
|
||||
id: null,
|
||||
networkId: aNetId,
|
||||
threshold: threshold,
|
||||
absoluteThreshold: null,
|
||||
startTime: options.startTime,
|
||||
data: options.data,
|
||||
pageURL: options.pageURL,
|
||||
manifestURL: options.manifestURL
|
||||
};
|
||||
|
||||
self._updateThreshold(newAlarm, function onUpdate(error, _threshold) {
|
||||
if (error) {
|
||||
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
|
||||
{ id: msg.id, error: error, result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
newAlarm.id = newId;
|
||||
self._setAlarm(newAlarm, function onSet(error, success) {
|
||||
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
|
||||
{ id: msg.id, error: error, result: newId });
|
||||
|
||||
if (error == "InvalidStateError") {
|
||||
self._fireAlarm(newAlarm);
|
||||
newAlarm.absoluteThreshold = _threshold.absoluteThreshold;
|
||||
self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
|
||||
if (error) {
|
||||
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
|
||||
{ id: msg.id, error: error, result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
newAlarm.id = newId;
|
||||
self._setAlarm(newAlarm, function onSet(error, success) {
|
||||
mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
|
||||
{ id: msg.id, error: error, result: newId });
|
||||
|
||||
if (error == "InvalidStateError") {
|
||||
self._fireAlarm(newAlarm);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -933,8 +970,9 @@ this.NetworkStatsService = {
|
|||
|
||||
_setAlarm: function _setAlarm(aAlarm, aCallback) {
|
||||
let currentAlarm = this._currentAlarms[aAlarm.networkId];
|
||||
if (Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
|
||||
aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) {
|
||||
if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
|
||||
aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) ||
|
||||
this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) {
|
||||
aCallback(null, true);
|
||||
return;
|
||||
}
|
||||
|
@ -985,6 +1023,14 @@ this.NetworkStatsService = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// There are no stats for the network of the alarm, set them to default 0 in
|
||||
// order to be able to calculate the offset, systemThreshold and
|
||||
// absoluteThreshold.
|
||||
result = { rxTotalBytes: 0, txTotalBytes: 0,
|
||||
rxSystemBytes: 0, txSystemBytes: 0 };
|
||||
}
|
||||
|
||||
let offset = aAlarm.threshold - result.rxTotalBytes - result.txTotalBytes;
|
||||
|
||||
// Alarm set to a threshold lower than current rx/tx bytes.
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
const NETWORK_STATUS_READY = 0;
|
||||
const NETWORK_STATUS_STANDBY = 1;
|
||||
const NETWORK_STATUS_AWAY = 2;
|
||||
|
||||
function getNetworks(callback) {
|
||||
NetworkStatsService._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
|
||||
callback(aError, aResult);
|
||||
|
@ -85,9 +89,18 @@ add_test(function test_updateQueueIndex() {
|
|||
});
|
||||
|
||||
add_test(function test_updateAllStats() {
|
||||
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_READY;
|
||||
NetworkStatsService.updateAllStats(function(success, msg) {
|
||||
do_check_eq(success, true);
|
||||
run_next_test();
|
||||
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_STANDBY;
|
||||
NetworkStatsService.updateAllStats(function(success, msg) {
|
||||
do_check_eq(success, true);
|
||||
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_AWAY;
|
||||
NetworkStatsService.updateAllStats(function(success, msg) {
|
||||
do_check_eq(success, true);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -189,6 +202,8 @@ add_test(function test_setAlarm_invalid_threshold() {
|
|||
pageURL: testPageURL,
|
||||
manifestURL: testManifestURL };
|
||||
|
||||
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_READY;
|
||||
|
||||
NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
|
||||
do_check_eq(error, "InvalidStateError");
|
||||
run_next_test();
|
||||
|
|
|
@ -93,9 +93,6 @@ let PaymentManager = {
|
|||
if (!pr) {
|
||||
continue;
|
||||
}
|
||||
if (!(pr instanceof Ci.nsIDOMPaymentRequestInfo)) {
|
||||
return;
|
||||
}
|
||||
// We consider jwt type repetition an error.
|
||||
if (jwtTypes[pr.type]) {
|
||||
this.paymentFailed(requestId,
|
||||
|
@ -342,17 +339,7 @@ let PaymentManager = {
|
|||
}
|
||||
|
||||
let pldRequest = payloadObject.request;
|
||||
let request = Cc["@mozilla.org/payment/request-info;1"]
|
||||
.createInstance(Ci.nsIDOMPaymentRequestInfo);
|
||||
if (!request) {
|
||||
this.paymentFailed(aRequestId,
|
||||
"INTERNAL_ERROR_ERROR_CREATING_PAY_REQUEST");
|
||||
return true;
|
||||
}
|
||||
request.wrappedJSObject.init(aJwt,
|
||||
payloadObject.typ,
|
||||
provider.name);
|
||||
return request;
|
||||
return { jwt: aJwt, type: payloadObject.typ, providerName: provider.name };
|
||||
},
|
||||
|
||||
showPaymentFlow: function showPaymentFlow(aRequestId,
|
||||
|
|
|
@ -4,6 +4,3 @@ category JavaScript-navigator-property mozPay @mozilla.org/payment/content-helpe
|
|||
|
||||
component {b8bce4e7-fbf0-4719-a634-b1bf9018657c} PaymentFlowInfo.js
|
||||
contract @mozilla.org/payment/flow-info;1 {b8bce4e7-fbf0-4719-a634-b1bf9018657c}
|
||||
|
||||
component {0a58c67d-f003-48da-81d1-bd8f605f4b1c} PaymentRequestInfo.js
|
||||
contract @mozilla.org/payment/request-info;1 {0a58c67d-f003-48da-81d1-bd8f605f4b1c}
|
||||
|
|
|
@ -1,41 +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} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const PAYMENTREQUESTINFO_CID =
|
||||
Components.ID("{0a58c67d-f003-48da-81d1-bd8f605f4b1c}");
|
||||
|
||||
// nsIDOMPaymentRequestInfo
|
||||
|
||||
function PaymentRequestInfo() {
|
||||
this.wrappedJSObject = this;
|
||||
};
|
||||
|
||||
PaymentRequestInfo.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMPaymentRequestInfo]),
|
||||
classID: PAYMENTREQUESTINFO_CID,
|
||||
classInfo: XPCOMUtils.generateCI({
|
||||
classID: PAYMENTREQUESTINFO_CID,
|
||||
contractID: "@mozilla.org/payment/request-info;1",
|
||||
classDescription: "Payment request information",
|
||||
flags: Ci.nsIClassInfo.DOM_OBJECT,
|
||||
interfaces: [Ci.nsIDOMPaymentRequestInfo]
|
||||
}),
|
||||
jwt: null,
|
||||
type: null,
|
||||
providerName: null,
|
||||
|
||||
init: function init(aJwt, aType, aProviderName) {
|
||||
this.jwt = aJwt;
|
||||
this.type = aType;
|
||||
this.providerName = aProviderName;
|
||||
}
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentRequestInfo]);
|
|
@ -5,7 +5,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIDOMPaymentRequestInfo.idl',
|
||||
'nsINavigatorPayment.idl',
|
||||
'nsIPaymentFlowInfo.idl',
|
||||
'nsIPaymentUIGlue.idl',
|
||||
|
|
|
@ -1,19 +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/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(93462984-0e9a-4016-bdb4-a24a88c08a29)]
|
||||
interface nsIDOMPaymentRequestInfo : nsISupports
|
||||
{
|
||||
// Base64 encoded and digitally signed payment request.
|
||||
readonly attribute DOMString jwt;
|
||||
|
||||
// JWT type that identifies the payment provider owner of the payment request
|
||||
// format.
|
||||
readonly attribute DOMString type;
|
||||
|
||||
// Payment provider name.
|
||||
readonly attribute DOMString providerName;
|
||||
};
|
|
@ -16,6 +16,5 @@ EXTRA_COMPONENTS += [
|
|||
'Payment.js',
|
||||
'Payment.manifest',
|
||||
'PaymentFlowInfo.js',
|
||||
'PaymentRequestInfo.js',
|
||||
]
|
||||
|
||||
|
|
|
@ -1000,10 +1000,6 @@ const ScreenPoint AsyncPanZoomController::GetVelocityVector() {
|
|||
return ScreenPoint(mX.GetVelocity(), mY.GetVelocity());
|
||||
}
|
||||
|
||||
const gfx::Point AsyncPanZoomController::GetAccelerationVector() {
|
||||
return gfx::Point(mX.GetAccelerationFactor(), mY.GetAccelerationFactor());
|
||||
}
|
||||
|
||||
void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle, TouchBehaviorFlags aBehavior) {
|
||||
// Handling of cross sliding will need to be added in this method after touch-action released
|
||||
// enabled by default.
|
||||
|
@ -1315,7 +1311,6 @@ EnlargeDisplayPortAlongAxis(float* aOutOffset, float* aOutLength,
|
|||
const CSSRect AsyncPanZoomController::CalculatePendingDisplayPort(
|
||||
const FrameMetrics& aFrameMetrics,
|
||||
const ScreenPoint& aVelocity,
|
||||
const gfx::Point& aAcceleration,
|
||||
double aEstimatedPaintDuration)
|
||||
{
|
||||
// convert to milliseconds
|
||||
|
@ -1349,10 +1344,9 @@ const CSSRect AsyncPanZoomController::CalculatePendingDisplayPort(
|
|||
displayPort = displayPort.ForceInside(scrollableRect) - scrollOffset;
|
||||
|
||||
APZC_LOG_FM(aFrameMetrics,
|
||||
"Calculated displayport as (%f %f %f %f) from velocity (%f %f) acceleration (%f %f) paint time %f metrics",
|
||||
"Calculated displayport as (%f %f %f %f) from velocity (%f %f) paint time %f metrics",
|
||||
displayPort.x, displayPort.y, displayPort.width, displayPort.height,
|
||||
aVelocity.x, aVelocity.y, aAcceleration.x, aAcceleration.y,
|
||||
(float)estimatedPaintDurationMillis);
|
||||
aVelocity.x, aVelocity.y, (float)estimatedPaintDurationMillis);
|
||||
|
||||
return displayPort;
|
||||
}
|
||||
|
@ -1371,7 +1365,6 @@ void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics)
|
|||
aFrameMetrics.mDisplayPort =
|
||||
CalculatePendingDisplayPort(aFrameMetrics,
|
||||
GetVelocityVector(),
|
||||
GetAccelerationVector(),
|
||||
mPaintThrottler.AverageDuration().ToSeconds());
|
||||
|
||||
// If we're trying to paint what we already think is painted, discard this
|
||||
|
@ -1747,7 +1740,6 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
|
|||
endZoomToMetrics.mDisplayPort =
|
||||
CalculatePendingDisplayPort(endZoomToMetrics,
|
||||
ScreenPoint(0,0),
|
||||
gfx::Point(0,0),
|
||||
0);
|
||||
|
||||
StartAnimation(new ZoomAnimation(
|
||||
|
|
|
@ -240,7 +240,6 @@ public:
|
|||
static const CSSRect CalculatePendingDisplayPort(
|
||||
const FrameMetrics& aFrameMetrics,
|
||||
const ScreenPoint& aVelocity,
|
||||
const gfx::Point& aAcceleration,
|
||||
double aEstimatedPaintDuration);
|
||||
|
||||
/**
|
||||
|
@ -438,11 +437,6 @@ protected:
|
|||
*/
|
||||
const ScreenPoint GetVelocityVector();
|
||||
|
||||
/**
|
||||
* Gets a vector of the acceleration factors of each axis.
|
||||
*/
|
||||
const gfx::Point GetAccelerationVector();
|
||||
|
||||
/**
|
||||
* Gets a reference to the first touch point from a MultiTouchInput. This
|
||||
* gets only the first one and assumes the rest are either missing or not
|
||||
|
|
|
@ -33,22 +33,6 @@ static float gMaxEventAcceleration = 999.0f;
|
|||
*/
|
||||
static float gFlingFriction = 0.002f;
|
||||
|
||||
/**
|
||||
* Threshold for velocity beneath which we turn off any acceleration we had
|
||||
* during repeated flings.
|
||||
*/
|
||||
static float gVelocityThreshold = 0.14f;
|
||||
|
||||
/**
|
||||
* Amount of acceleration we multiply in each time the user flings in one
|
||||
* direction. Every time they let go of the screen, we increase the acceleration
|
||||
* by this amount raised to the power of the amount of times they have let go,
|
||||
* times two (to make the curve steeper). This stops if the user lets go and we
|
||||
* slow down enough, or if they put their finger down without moving it for a
|
||||
* moment (or in the opposite direction).
|
||||
*/
|
||||
static float gAccelerationMultiplier = 1.125f;
|
||||
|
||||
/**
|
||||
* When flinging, if the velocity goes below this number, we just stop the
|
||||
* animation completely. This is to prevent asymptotically approaching 0
|
||||
|
@ -67,8 +51,6 @@ static void ReadAxisPrefs()
|
|||
{
|
||||
Preferences::AddFloatVarCache(&gMaxEventAcceleration, "apz.max_event_acceleration", gMaxEventAcceleration);
|
||||
Preferences::AddFloatVarCache(&gFlingFriction, "apz.fling_friction", gFlingFriction);
|
||||
Preferences::AddFloatVarCache(&gVelocityThreshold, "apz.velocity_threshold", gVelocityThreshold);
|
||||
Preferences::AddFloatVarCache(&gAccelerationMultiplier, "apz.acceleration_multiplier", gAccelerationMultiplier);
|
||||
Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "apz.fling_stopped_threshold", gFlingStoppedThreshold);
|
||||
Preferences::AddUintVarCache(&gMaxVelocityQueueSize, "apz.max_velocity_queue_size", gMaxVelocityQueueSize);
|
||||
}
|
||||
|
@ -100,7 +82,6 @@ static void InitAxisPrefs()
|
|||
Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
|
||||
: mPos(0),
|
||||
mVelocity(0.0f),
|
||||
mAcceleration(0),
|
||||
mAxisLocked(false),
|
||||
mAsyncPanZoomController(aAsyncPanZoomController)
|
||||
{
|
||||
|
@ -110,15 +91,6 @@ Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
|
|||
void Axis::UpdateWithTouchAtDevicePoint(int32_t aPos, const TimeDuration& aTimeDelta) {
|
||||
float newVelocity = mAxisLocked ? 0 : (mPos - aPos) / aTimeDelta.ToMilliseconds();
|
||||
|
||||
bool curVelocityBelowThreshold = fabsf(newVelocity) < gVelocityThreshold;
|
||||
bool directionChange = (mVelocity > 0) != (newVelocity > 0);
|
||||
|
||||
// If we've changed directions, or the current velocity threshold, stop any
|
||||
// acceleration we've accumulated.
|
||||
if (directionChange || curVelocityBelowThreshold) {
|
||||
mAcceleration = 0;
|
||||
}
|
||||
|
||||
mVelocity = newVelocity;
|
||||
mPos = aPos;
|
||||
|
||||
|
@ -145,23 +117,17 @@ float Axis::AdjustDisplacement(float aDisplacement, float& aOverscrollAmountOut,
|
|||
if (aScrollingDisabled) {
|
||||
// Scrolling is disabled on this axis, stop scrolling.
|
||||
aOverscrollAmountOut = aDisplacement;
|
||||
mAcceleration = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fabsf(mVelocity) < gVelocityThreshold) {
|
||||
mAcceleration = 0;
|
||||
}
|
||||
float displacement = aDisplacement;
|
||||
|
||||
float accelerationFactor = GetAccelerationFactor();
|
||||
float displacement = aDisplacement * accelerationFactor;
|
||||
// If this displacement will cause an overscroll, throttle it. Can potentially
|
||||
// bring it to 0 even if the velocity is high.
|
||||
if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) {
|
||||
// No need to have a velocity along this axis anymore; it won't take us
|
||||
// anywhere, so we're just spinning needlessly.
|
||||
mVelocity = 0.0f;
|
||||
mAcceleration = 0;
|
||||
aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement);
|
||||
displacement -= aOverscrollAmountOut;
|
||||
}
|
||||
|
@ -177,8 +143,6 @@ float Axis::PanDistance(float aPos) {
|
|||
}
|
||||
|
||||
void Axis::EndTouch() {
|
||||
mAcceleration++;
|
||||
|
||||
// Calculate the mean velocity and empty the queue.
|
||||
int count = mVelocityQueue.Length();
|
||||
if (count) {
|
||||
|
@ -193,7 +157,6 @@ void Axis::EndTouch() {
|
|||
|
||||
void Axis::CancelTouch() {
|
||||
mVelocity = 0.0f;
|
||||
mAcceleration = 0;
|
||||
while (!mVelocityQueue.IsEmpty()) {
|
||||
mVelocityQueue.RemoveElementAt(0);
|
||||
}
|
||||
|
@ -302,10 +265,6 @@ float Axis::GetVelocity() {
|
|||
return mAxisLocked ? 0 : mVelocity;
|
||||
}
|
||||
|
||||
float Axis::GetAccelerationFactor() {
|
||||
return powf(gAccelerationMultiplier, std::max(0, (mAcceleration - 4) * 3));
|
||||
}
|
||||
|
||||
float Axis::GetCompositionEnd() {
|
||||
return GetOrigin() + GetCompositionLength();
|
||||
}
|
||||
|
|
|
@ -70,12 +70,11 @@ public:
|
|||
void CancelTouch();
|
||||
|
||||
/**
|
||||
* Takes a requested displacement to the position of this axis, and adjusts
|
||||
* it to account for acceleration (which might increase the displacement),
|
||||
* overscroll (which might decrease the displacement; this is to prevent the
|
||||
* viewport from overscrolling the page rect), and axis locking (which might
|
||||
* prevent any displacement from happening). If overscroll ocurred, its amount
|
||||
* is written to |aOverscrollAmountOut|.
|
||||
* Takes a requested displacement to the position of this axis, and adjusts it
|
||||
* to account for overscroll (which might decrease the displacement; this is
|
||||
* to prevent the viewport from overscrolling the page rect), and axis locking
|
||||
* (which might prevent any displacement from happening). If overscroll
|
||||
* ocurred, its amount is written to |aOverscrollAmountOut|.
|
||||
* The adjusted displacement is returned.
|
||||
*
|
||||
* aScrollingDisabled is used to indicate that no scrolling should happen
|
||||
|
@ -128,12 +127,6 @@ public:
|
|||
*/
|
||||
float GetExcess();
|
||||
|
||||
/**
|
||||
* Gets the factor of acceleration applied to the velocity, based on the
|
||||
* amount of flings that have been done successively.
|
||||
*/
|
||||
float GetAccelerationFactor();
|
||||
|
||||
/**
|
||||
* Gets the raw velocity of this axis at this moment.
|
||||
*/
|
||||
|
@ -188,12 +181,6 @@ protected:
|
|||
int32_t mPos;
|
||||
int32_t mStartPos;
|
||||
float mVelocity;
|
||||
// Acceleration is represented by an int, which is the power we raise a
|
||||
// constant to and then multiply the velocity by whenever it is sampled. We do
|
||||
// this only when we detect that the user wants to do a fast fling; that is,
|
||||
// they are flinging multiple times in a row very quickly, probably trying to
|
||||
// reach one of the extremes of the page.
|
||||
int32_t mAcceleration;
|
||||
bool mAxisLocked; // Whether movement on this axis is locked.
|
||||
AsyncPanZoomController* mAsyncPanZoomController;
|
||||
nsTArray<float> mVelocityQueue;
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
#include "mozilla/ipc/MessageLink.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
|
||||
#if defined(ANDROID) && defined(DEBUG)
|
||||
#include <android/log.h>
|
||||
#endif
|
||||
|
||||
// WARNING: this takes into account the private, special-message-type
|
||||
// enum in ipc_channel.h. They need to be kept in sync.
|
||||
namespace {
|
||||
|
|
|
@ -1825,11 +1825,13 @@ def _generateMessageClass(clsname, msgid, prettyName, compress):
|
|||
# generate a logging function
|
||||
# 'pfx' will be something like "[FooParent] sent"
|
||||
pfxvar = ExprVar('__pfx')
|
||||
outfvar = ExprVar('__outf')
|
||||
otherprocess = ExprVar('__otherProcess')
|
||||
receiving = ExprVar('__receiving')
|
||||
logger = MethodDefn(MethodDecl(
|
||||
'Log',
|
||||
params=([ Decl(Type('std::string', const=1, ref=1), pfxvar.name),
|
||||
Decl(Type('FILE', ptr=True), outfvar.name) ]),
|
||||
Decl(Type('base::ProcessHandle'), otherprocess.name),
|
||||
Decl(Type('bool'), receiving.name) ]),
|
||||
const=1))
|
||||
# TODO/cjones: allow selecting what information is printed to
|
||||
# the log
|
||||
|
@ -1843,9 +1845,12 @@ def _generateMessageClass(clsname, msgid, prettyName, compress):
|
|||
StmtExpr(ExprCall(
|
||||
ExprVar('StringAppendF'),
|
||||
args=[ ExprAddrOf(msgvar),
|
||||
ExprLiteral.String('[time:%" PRId64 "][%d]'),
|
||||
ExprLiteral.String('[time:%" PRId64 "][%d%s%d]'),
|
||||
ExprCall(ExprVar('PR_Now')),
|
||||
ExprCall(ExprVar('base::GetCurrentProcId')) ])),
|
||||
ExprCall(ExprVar('base::GetCurrentProcId')),
|
||||
ExprConditional(receiving, ExprLiteral.String('<-'),
|
||||
ExprLiteral.String('->')),
|
||||
otherprocess ])),
|
||||
appendToMsg(pfxvar),
|
||||
appendToMsg(ExprLiteral.String(clsname +'(')),
|
||||
Whitespace.NL
|
||||
|
@ -1855,10 +1860,21 @@ def _generateMessageClass(clsname, msgid, prettyName, compress):
|
|||
|
||||
logger.addstmt(appendToMsg(ExprLiteral.String('[TODO])\\n')))
|
||||
|
||||
logger.addstmts([
|
||||
CppDirective('ifdef', 'ANDROID'),
|
||||
StmtExpr(ExprCall(
|
||||
ExprVar('__android_log_write'),
|
||||
args=[ ExprVar('ANDROID_LOG_INFO'),
|
||||
ExprLiteral.String('GeckoIPC'),
|
||||
ExprCall(ExprSelect(msgvar, '.', 'c_str')) ])),
|
||||
CppDirective('endif')
|
||||
])
|
||||
|
||||
# and actually print the log message
|
||||
logger.addstmt(StmtExpr(ExprCall(
|
||||
ExprVar('fputs'),
|
||||
args=[ ExprCall(ExprSelect(msgvar, '.', 'c_str')), outfvar ])))
|
||||
args=[ ExprCall(ExprSelect(msgvar, '.', 'c_str')),
|
||||
ExprVar('stderr') ])))
|
||||
|
||||
cls.addstmt(logger)
|
||||
|
||||
|
@ -4929,7 +4945,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
+ sendstmts)
|
||||
|
||||
destmts = self.deserializeReply(
|
||||
md, ExprAddrOf(replyvar), self.side, errfnSend)
|
||||
md, ExprAddrOf(replyvar), self.side, errfnSend, actorvar)
|
||||
ifsendok = StmtIf(ExprLiteral.FALSE)
|
||||
ifsendok.addifstmts(destmts)
|
||||
ifsendok.addifstmts([ Whitespace.NL,
|
||||
|
@ -5165,7 +5181,8 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
'.', 'set_name'),
|
||||
args=[ ExprLiteral.String(md.prettyMsgName(self.protocol.name
|
||||
+'::')) ])),
|
||||
self.logMessage(md, md.msgCast(msgexpr), 'Received '),
|
||||
self.logMessage(md, md.msgCast(msgexpr), 'Received ',
|
||||
receiving=True),
|
||||
self.profilerLabel('Recv', md.decl.progname),
|
||||
Whitespace.NL
|
||||
])
|
||||
|
@ -5200,10 +5217,10 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
return stmts
|
||||
|
||||
|
||||
def deserializeReply(self, md, replyexpr, side, errfn):
|
||||
def deserializeReply(self, md, replyexpr, side, errfn, actor=None):
|
||||
stmts = [ Whitespace.NL,
|
||||
self.logMessage(md, md.replyCast(replyexpr),
|
||||
'Received reply ') ]
|
||||
'Received reply ', actor, receiving=True) ]
|
||||
if 0 == len(md.returns):
|
||||
return stmts
|
||||
|
||||
|
@ -5226,7 +5243,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
return (
|
||||
sendok,
|
||||
([ Whitespace.NL,
|
||||
self.logMessage(md, msgexpr, 'Sending '),
|
||||
self.logMessage(md, msgexpr, 'Sending ', actor),
|
||||
self.profilerLabel('AsyncSend', md.decl.progname) ]
|
||||
+ self.transition(md, 'out', actor)
|
||||
+ [ Whitespace.NL,
|
||||
|
@ -5243,7 +5260,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
return (
|
||||
sendok,
|
||||
([ Whitespace.NL,
|
||||
self.logMessage(md, msgexpr, 'Sending '),
|
||||
self.logMessage(md, msgexpr, 'Sending ', actor),
|
||||
self.profilerLabel('Send', md.decl.progname) ]
|
||||
+ self.transition(md, 'out', actor)
|
||||
+ [ Whitespace.NL,
|
||||
|
@ -5312,13 +5329,14 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
decl.ret = md.actorDecl().bareType(self.side)
|
||||
return decl
|
||||
|
||||
def logMessage(self, md, msgptr, pfx):
|
||||
def logMessage(self, md, msgptr, pfx, actor=None, receiving=False):
|
||||
actorname = _actorName(self.protocol.name, self.side)
|
||||
return _ifLogging([
|
||||
StmtExpr(ExprCall(
|
||||
ExprSelect(msgptr, '->', 'Log'),
|
||||
args=[ ExprLiteral.String('['+ actorname +'] '+ pfx),
|
||||
ExprVar('stderr') ])) ])
|
||||
|
||||
return _ifLogging([ StmtExpr(ExprCall(
|
||||
ExprSelect(msgptr, '->', 'Log'),
|
||||
args=[ ExprLiteral.String('['+ actorname +'] '+ pfx),
|
||||
self.protocol.callOtherProcess(actor),
|
||||
ExprLiteral.TRUE if receiving else ExprLiteral.FALSE ])) ])
|
||||
|
||||
def profilerLabel(self, tag, msgname):
|
||||
return StmtExpr(ExprCall(ExprVar('PROFILER_LABEL'),
|
||||
|
|
|
@ -174,6 +174,9 @@ public:
|
|||
RefPtr<UnixSocketConsumer> mConsumer;
|
||||
|
||||
private:
|
||||
|
||||
void FireSocketError();
|
||||
|
||||
/**
|
||||
* libevent triggered functions that reads data from socket when available and
|
||||
* guarenteed non-blocking. Only to be called on IO thread.
|
||||
|
@ -487,29 +490,47 @@ void ShutdownSocketTask::Run()
|
|||
}
|
||||
|
||||
void
|
||||
UnixSocketImpl::Accept()
|
||||
UnixSocketImpl::FireSocketError()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
if (!mConnector) {
|
||||
NS_WARNING("No connector object available!");
|
||||
return;
|
||||
}
|
||||
// Clean up watchers, statuses, fds
|
||||
mReadWatcher.StopWatchingFileDescriptor();
|
||||
mWriteWatcher.StopWatchingFileDescriptor();
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
mFd.reset(-1);
|
||||
|
||||
// Tell the main thread we've errored
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
}
|
||||
|
||||
void
|
||||
UnixSocketImpl::Accept()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(mConnector);
|
||||
|
||||
// This will set things we don't particularly care about, but it will hand
|
||||
// back the correct structure size which is what we do care about.
|
||||
if (!mConnector->CreateAddr(true, mAddrSize, mAddr, nullptr)) {
|
||||
NS_WARNING("Cannot create socket address!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFd.get() < 0) {
|
||||
mFd = mConnector->Create();
|
||||
if (mFd.get() < 0) {
|
||||
NS_WARNING("Cannot create socket fd!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetSocketFlags()) {
|
||||
NS_WARNING("Cannot set socket flags!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -517,6 +538,7 @@ UnixSocketImpl::Accept()
|
|||
#ifdef DEBUG
|
||||
CHROMIUM_LOG("...bind(%d) gave errno %d", mFd.get(), errno);
|
||||
#endif
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -524,15 +546,13 @@ UnixSocketImpl::Accept()
|
|||
#ifdef DEBUG
|
||||
CHROMIUM_LOG("...listen(%d) gave errno %d", mFd.get(), errno);
|
||||
#endif
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mConnector->SetUpListenSocket(mFd)) {
|
||||
NS_WARNING("Could not set up listen socket!");
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -545,15 +565,13 @@ void
|
|||
UnixSocketImpl::Connect()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
if (!mConnector) {
|
||||
NS_WARNING("No connector object available!");
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(mConnector);
|
||||
|
||||
if (mFd.get() < 0) {
|
||||
mFd = mConnector->Create();
|
||||
if (mFd.get() < 0) {
|
||||
NS_WARNING("Cannot create socket fd!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -562,15 +580,14 @@ UnixSocketImpl::Connect()
|
|||
|
||||
if (!mConnector->CreateAddr(false, mAddrSize, mAddr, mAddress.get())) {
|
||||
NS_WARNING("Cannot create socket address!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Select non-blocking IO.
|
||||
if (-1 == fcntl(mFd.get(), F_SETFL, O_NONBLOCK)) {
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
NS_WARNING("Cannot set nonblock!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -583,20 +600,12 @@ UnixSocketImpl::Connect()
|
|||
int current_opts = fcntl(mFd.get(), F_GETFL, 0);
|
||||
if (-1 == current_opts) {
|
||||
NS_WARNING("Cannot get socket opts!");
|
||||
mFd.reset(-1);
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
if (-1 == fcntl(mFd.get(), F_SETFL, current_opts & ~O_NONBLOCK)) {
|
||||
NS_WARNING("Cannot set socket opts to blocking!");
|
||||
mFd.reset(-1);
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -616,20 +625,19 @@ UnixSocketImpl::Connect()
|
|||
#if DEBUG
|
||||
CHROMIUM_LOG("Socket connect errno=%d\n", errno);
|
||||
#endif
|
||||
mFd.reset(-1);
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetSocketFlags()) {
|
||||
NS_WARNING("Cannot set socket flags!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mConnector->SetUp(mFd)) {
|
||||
NS_WARNING("Could not set up socket!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -862,30 +870,19 @@ UnixSocketImpl::OnFileCanWriteWithoutBlocking(int aFd)
|
|||
|
||||
if (ret || error) {
|
||||
NS_WARNING("getsockopt failure on async socket connect!");
|
||||
mFd.reset(-1);
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetSocketFlags()) {
|
||||
mFd.reset(-1);
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
NS_WARNING("Cannot set socket flags!");
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mConnector->SetUp(mFd)) {
|
||||
NS_WARNING("Could not set up socket!");
|
||||
mFd.reset(-1);
|
||||
nsRefPtr<OnSocketEventTask> t =
|
||||
new OnSocketEventTask(this, OnSocketEventTask::CONNECT_ERROR);
|
||||
NS_DispatchToMainThread(t);
|
||||
mConnectionStatus = SOCKET_DISCONNECTED;
|
||||
FireSocketError();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
|
|||
import org.mozilla.gecko.gfx.LayerMarginsAnimator;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
||||
import org.mozilla.gecko.health.BrowserHealthReporter;
|
||||
import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.home.BrowserSearch;
|
||||
import org.mozilla.gecko.home.HomePager;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
|
@ -29,6 +31,7 @@ import org.mozilla.gecko.prompts.Prompt;
|
|||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.EventDispatcher;
|
||||
import org.mozilla.gecko.util.GamepadUtils;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.StringUtils;
|
||||
|
@ -43,6 +46,7 @@ import org.json.JSONObject;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -2583,4 +2587,19 @@ abstract public class BrowserApp extends GeckoApp
|
|||
mShowActionModeEndAnimation = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthRecorder createHealthRecorder(final Context context,
|
||||
final String profilePath,
|
||||
final EventDispatcher dispatcher,
|
||||
final String osLocale,
|
||||
final String appLocale,
|
||||
final SessionInformation previousSession) {
|
||||
return new BrowserHealthRecorder(context,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
appLocale,
|
||||
previousSession);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ import org.mozilla.gecko.prompts.PromptService;
|
|||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuInflater;
|
||||
import org.mozilla.gecko.menu.MenuPanel;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder;
|
||||
import org.mozilla.gecko.health.BrowserHealthRecorder.SessionInformation;
|
||||
import org.mozilla.gecko.health.HealthRecorder;
|
||||
import org.mozilla.gecko.health.SessionInformation;
|
||||
import org.mozilla.gecko.health.StubbedHealthRecorder;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.updater.UpdateService;
|
||||
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
||||
|
@ -214,7 +215,7 @@ public abstract class GeckoApp
|
|||
|
||||
private String mPrivateBrowsingSession;
|
||||
|
||||
private volatile BrowserHealthRecorder mHealthRecorder = null;
|
||||
private volatile HealthRecorder mHealthRecorder = null;
|
||||
|
||||
private int mSignalStrenth;
|
||||
private PhoneStateListener mPhoneStateListener = null;
|
||||
|
@ -582,7 +583,7 @@ public abstract class GeckoApp
|
|||
// know that mHealthRecorder will exist. That doesn't stop us being
|
||||
// paranoid.
|
||||
// This method is cheap, so don't spawn a new runnable.
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
|
||||
}
|
||||
|
@ -1303,7 +1304,7 @@ public abstract class GeckoApp
|
|||
// of the activity itself.
|
||||
final String profilePath = getProfile().getDir().getAbsolutePath();
|
||||
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
|
||||
Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
|
||||
Log.i(LOGTAG, "Creating HealthRecorder.");
|
||||
|
||||
final String osLocale = Locale.getDefault().toString();
|
||||
String appLocale = LocaleManager.getAndApplyPersistedLocale();
|
||||
|
@ -1313,12 +1314,12 @@ public abstract class GeckoApp
|
|||
appLocale = osLocale;
|
||||
}
|
||||
|
||||
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
appLocale,
|
||||
previousSession);
|
||||
mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
|
||||
profilePath,
|
||||
dispatcher,
|
||||
osLocale,
|
||||
appLocale,
|
||||
previousSession);
|
||||
|
||||
final String uiLocale = appLocale;
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
|
@ -1599,7 +1600,7 @@ public abstract class GeckoApp
|
|||
ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.recordJavaStartupTime(javaDuration);
|
||||
}
|
||||
|
@ -1962,7 +1963,7 @@ public abstract class GeckoApp
|
|||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Now construct the new session on BrowserHealthRecorder's behalf. We do this here
|
||||
// Now construct the new session on HealthRecorder's behalf. We do this here
|
||||
// so it can benefit from a single near-startup prefs commit.
|
||||
SessionInformation currentSession = new SessionInformation(now, realTime);
|
||||
|
||||
|
@ -1972,7 +1973,7 @@ public abstract class GeckoApp
|
|||
currentSession.recordBegin(editor);
|
||||
editor.commit();
|
||||
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.setCurrentSession(currentSession);
|
||||
} else {
|
||||
|
@ -1995,7 +1996,7 @@ public abstract class GeckoApp
|
|||
@Override
|
||||
public void onPause()
|
||||
{
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
final Context context = this;
|
||||
|
||||
// In some way it's sad that Android will trigger StrictMode warnings
|
||||
|
@ -2114,9 +2115,9 @@ public abstract class GeckoApp
|
|||
SmsManager.getInstance().shutdown();
|
||||
}
|
||||
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
mHealthRecorder = null;
|
||||
if (rec != null) {
|
||||
if (rec != null && rec.isEnabled()) {
|
||||
// Closing a BrowserHealthRecorder could incur a write.
|
||||
ThreadUtils.postToBackgroundThread(new Runnable() {
|
||||
@Override
|
||||
|
@ -2773,7 +2774,7 @@ public abstract class GeckoApp
|
|||
|
||||
/**
|
||||
* Use LocaleManager to change our persisted and current locales,
|
||||
* and poke BrowserHealthRecorder to tell it of our changed state.
|
||||
* and poke HealthRecorder to tell it of our changed state.
|
||||
*/
|
||||
private void setLocale(final String locale) {
|
||||
if (locale == null) {
|
||||
|
@ -2784,15 +2785,17 @@ public abstract class GeckoApp
|
|||
return;
|
||||
}
|
||||
|
||||
final BrowserHealthRecorder rec = mHealthRecorder;
|
||||
if (rec == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean startNewSession = true;
|
||||
final boolean shouldRestart = false;
|
||||
rec.onAppLocaleChanged(resultant);
|
||||
rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
|
||||
|
||||
// If the HealthRecorder is not yet initialized (unlikely), the locale change won't
|
||||
// trigger a session transition and subsequent events will be recorded in an environment
|
||||
// with the wrong locale.
|
||||
final HealthRecorder rec = mHealthRecorder;
|
||||
if (rec != null) {
|
||||
rec.onAppLocaleChanged(resultant);
|
||||
rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
|
||||
}
|
||||
|
||||
if (!shouldRestart) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
|
@ -2827,4 +2830,14 @@ public abstract class GeckoApp
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected HealthRecorder createHealthRecorder(final Context context,
|
||||
final String profilePath,
|
||||
final EventDispatcher dispatcher,
|
||||
final String osLocale,
|
||||
final String appLocale,
|
||||
final SessionInformation previousSession) {
|
||||
// GeckoApp does not need to record any health information - return a stub.
|
||||
return new StubbedHealthRecorder();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.mozilla.gecko;
|
|||
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.home.HomeConfigInvalidator;
|
||||
import org.mozilla.gecko.mozglue.GeckoLoader;
|
||||
import org.mozilla.gecko.util.Clipboard;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
|
@ -68,6 +69,7 @@ public class GeckoApplication extends Application {
|
|||
GeckoBatteryManager.getInstance().start();
|
||||
GeckoNetworkManager.getInstance().init(getApplicationContext());
|
||||
MemoryMonitor.getInstance().init(getApplicationContext());
|
||||
HomeConfigInvalidator.getInstance().init(getApplicationContext());
|
||||
|
||||
mInited = true;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
*
|
||||
* Shut it down when you're done being a browser: {@link #close()}.
|
||||
*/
|
||||
public class BrowserHealthRecorder implements GeckoEventListener {
|
||||
public class BrowserHealthRecorder implements HealthRecorder, GeckoEventListener {
|
||||
private static final String LOG_TAG = "GeckoHealthRec";
|
||||
private static final String PREF_ACCEPT_LANG = "intl.accept_languages";
|
||||
private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
|
||||
|
@ -97,128 +97,10 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
private final ProfileInformationCache profileCache;
|
||||
private final EventDispatcher dispatcher;
|
||||
|
||||
public static class SessionInformation {
|
||||
private static final String LOG_TAG = "GeckoSessInfo";
|
||||
|
||||
public static final String PREFS_SESSION_START = "sessionStart";
|
||||
|
||||
public final long wallStartTime; // System wall clock.
|
||||
public final long realStartTime; // Realtime clock.
|
||||
|
||||
private final boolean wasOOM;
|
||||
private final boolean wasStopped;
|
||||
|
||||
private volatile long timedGeckoStartup = -1;
|
||||
private volatile long timedJavaStartup = -1;
|
||||
|
||||
// Current sessions don't (right now) care about wasOOM/wasStopped.
|
||||
// Eventually we might want to lift that logic out of GeckoApp.
|
||||
public SessionInformation(long wallTime, long realTime) {
|
||||
this(wallTime, realTime, false, false);
|
||||
}
|
||||
|
||||
// Previous sessions do...
|
||||
public SessionInformation(long wallTime, long realTime, boolean wasOOM, boolean wasStopped) {
|
||||
this.wallStartTime = wallTime;
|
||||
this.realStartTime = realTime;
|
||||
this.wasOOM = wasOOM;
|
||||
this.wasStopped = wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance from the supplied prefs object.
|
||||
*
|
||||
* This includes retrieving OOM/crash data, as well as timings.
|
||||
*
|
||||
* If no wallStartTime was found, that implies that the previous
|
||||
* session was correctly recorded, and an object with a zero
|
||||
* wallStartTime is returned.
|
||||
*/
|
||||
public static SessionInformation fromSharedPrefs(SharedPreferences prefs) {
|
||||
boolean wasOOM = prefs.getBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
|
||||
boolean wasStopped = prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
|
||||
long wallStartTime = prefs.getLong(PREFS_SESSION_START, 0L);
|
||||
long realStartTime = 0L;
|
||||
Log.d(LOG_TAG, "Building SessionInformation from prefs: " +
|
||||
wallStartTime + ", " + realStartTime + ", " +
|
||||
wasStopped + ", " + wasOOM);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance to 'split' the current
|
||||
* session.
|
||||
*/
|
||||
public static SessionInformation forRuntimeTransition() {
|
||||
final boolean wasOOM = false;
|
||||
final boolean wasStopped = true;
|
||||
final long wallStartTime = System.currentTimeMillis();
|
||||
final long realStartTime = android.os.SystemClock.elapsedRealtime();
|
||||
Log.v(LOG_TAG, "Recording runtime session transition: " +
|
||||
wallStartTime + ", " + realStartTime);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
public boolean wasKilled() {
|
||||
return wasOOM || !wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the beginning of this session to SharedPreferences by
|
||||
* recording our start time. If a session was already recorded, it is
|
||||
* overwritten (there can only be one running session at a time). Does
|
||||
* not commit the editor.
|
||||
*/
|
||||
public void recordBegin(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording start of session: " + this.wallStartTime);
|
||||
editor.putLong(PREFS_SESSION_START, this.wallStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the completion of this session to SharedPreferences by
|
||||
* deleting our start time. Does not commit the editor.
|
||||
*/
|
||||
public void recordCompletion(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording session done: " + this.wallStartTime);
|
||||
editor.remove(PREFS_SESSION_START);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON that we'll put in the DB for this session.
|
||||
*/
|
||||
public JSONObject getCompletionJSON(String reason, long realEndTime) throws JSONException {
|
||||
long durationSecs = (realEndTime - this.realStartTime) / 1000;
|
||||
JSONObject out = new JSONObject();
|
||||
out.put("r", reason);
|
||||
out.put("d", durationSecs);
|
||||
if (this.timedGeckoStartup > 0) {
|
||||
out.put("sg", this.timedGeckoStartup);
|
||||
}
|
||||
if (this.timedJavaStartup > 0) {
|
||||
out.put("sj", this.timedJavaStartup);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public JSONObject getCrashedJSON() throws JSONException {
|
||||
JSONObject out = new JSONObject();
|
||||
// We use ints here instead of booleans, because we're packing
|
||||
// stuff into JSON, and saving bytes in the DB is a worthwhile
|
||||
// goal.
|
||||
out.put("oom", this.wasOOM ? 1 : 0);
|
||||
out.put("stopped", this.wasStopped ? 1 : 0);
|
||||
out.put("r", "A");
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// We track previousSession to avoid order-of-initialization confusion. We
|
||||
// accept it in the constructor, and process it after init.
|
||||
private final SessionInformation previousSession;
|
||||
private volatile SessionInformation session = null;
|
||||
public SessionInformation getCurrentSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public void setCurrentSession(SessionInformation session) {
|
||||
this.session = session;
|
||||
|
@ -228,13 +110,13 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
if (this.session == null) {
|
||||
return;
|
||||
}
|
||||
this.session.timedGeckoStartup = duration;
|
||||
this.session.setTimedGeckoStartup(duration);
|
||||
}
|
||||
public void recordJavaStartupTime(long duration) {
|
||||
if (this.session == null) {
|
||||
return;
|
||||
}
|
||||
this.session.timedJavaStartup = duration;
|
||||
this.session.setTimedJavaStartup(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,6 +168,10 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down database connections, unregister event listeners, and perform
|
||||
* provider-specific uninitialization.
|
||||
|
@ -1017,4 +903,3 @@ public class BrowserHealthRecorder implements GeckoEventListener {
|
|||
session.recordCompletion(editor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.health;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* HealthRecorder is an interface into the Firefox Health Report storage system.
|
||||
*/
|
||||
public interface HealthRecorder {
|
||||
/**
|
||||
* Returns whether the Health Recorder is actively recording events.
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
|
||||
public void setCurrentSession(SessionInformation session);
|
||||
public void checkForOrphanSessions();
|
||||
|
||||
public void recordGeckoStartupTime(long duration);
|
||||
public void recordJavaStartupTime(long duration);
|
||||
public void recordSearch(final String engineID, final String location);
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor);
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment);
|
||||
|
||||
public void onAppLocaleChanged(String to);
|
||||
public void onAddonChanged(String id, JSONObject json);
|
||||
public void onAddonUninstalling(String id);
|
||||
public void onEnvironmentChanged();
|
||||
public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason);
|
||||
|
||||
public void close();
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.health;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.GeckoApp;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class SessionInformation {
|
||||
private static final String LOG_TAG = "GeckoSessInfo";
|
||||
|
||||
public static final String PREFS_SESSION_START = "sessionStart";
|
||||
|
||||
public final long wallStartTime; // System wall clock.
|
||||
public final long realStartTime; // Realtime clock.
|
||||
|
||||
private final boolean wasOOM;
|
||||
private final boolean wasStopped;
|
||||
|
||||
private volatile long timedGeckoStartup = -1;
|
||||
private volatile long timedJavaStartup = -1;
|
||||
|
||||
// Current sessions don't (right now) care about wasOOM/wasStopped.
|
||||
// Eventually we might want to lift that logic out of GeckoApp.
|
||||
public SessionInformation(long wallTime, long realTime) {
|
||||
this(wallTime, realTime, false, false);
|
||||
}
|
||||
|
||||
// Previous sessions do...
|
||||
public SessionInformation(long wallTime, long realTime, boolean wasOOM, boolean wasStopped) {
|
||||
this.wallStartTime = wallTime;
|
||||
this.realStartTime = realTime;
|
||||
this.wasOOM = wasOOM;
|
||||
this.wasStopped = wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance from the supplied prefs object.
|
||||
*
|
||||
* This includes retrieving OOM/crash data, as well as timings.
|
||||
*
|
||||
* If no wallStartTime was found, that implies that the previous
|
||||
* session was correctly recorded, and an object with a zero
|
||||
* wallStartTime is returned.
|
||||
*/
|
||||
public static SessionInformation fromSharedPrefs(SharedPreferences prefs) {
|
||||
boolean wasOOM = prefs.getBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
|
||||
boolean wasStopped = prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
|
||||
long wallStartTime = prefs.getLong(PREFS_SESSION_START, 0L);
|
||||
long realStartTime = 0L;
|
||||
Log.d(LOG_TAG, "Building SessionInformation from prefs: " +
|
||||
wallStartTime + ", " + realStartTime + ", " +
|
||||
wasStopped + ", " + wasOOM);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new SessionInformation instance to 'split' the current
|
||||
* session.
|
||||
*/
|
||||
public static SessionInformation forRuntimeTransition() {
|
||||
final boolean wasOOM = false;
|
||||
final boolean wasStopped = true;
|
||||
final long wallStartTime = System.currentTimeMillis();
|
||||
final long realStartTime = android.os.SystemClock.elapsedRealtime();
|
||||
Log.v(LOG_TAG, "Recording runtime session transition: " +
|
||||
wallStartTime + ", " + realStartTime);
|
||||
return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
|
||||
}
|
||||
|
||||
public boolean wasKilled() {
|
||||
return wasOOM || !wasStopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the beginning of this session to SharedPreferences by
|
||||
* recording our start time. If a session was already recorded, it is
|
||||
* overwritten (there can only be one running session at a time). Does
|
||||
* not commit the editor.
|
||||
*/
|
||||
public void recordBegin(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording start of session: " + this.wallStartTime);
|
||||
editor.putLong(PREFS_SESSION_START, this.wallStartTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the completion of this session to SharedPreferences by
|
||||
* deleting our start time. Does not commit the editor.
|
||||
*/
|
||||
public void recordCompletion(SharedPreferences.Editor editor) {
|
||||
Log.d(LOG_TAG, "Recording session done: " + this.wallStartTime);
|
||||
editor.remove(PREFS_SESSION_START);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON that we'll put in the DB for this session.
|
||||
*/
|
||||
public JSONObject getCompletionJSON(String reason, long realEndTime) throws JSONException {
|
||||
long durationSecs = (realEndTime - this.realStartTime) / 1000;
|
||||
JSONObject out = new JSONObject();
|
||||
out.put("r", reason);
|
||||
out.put("d", durationSecs);
|
||||
if (this.timedGeckoStartup > 0) {
|
||||
out.put("sg", this.timedGeckoStartup);
|
||||
}
|
||||
if (this.timedJavaStartup > 0) {
|
||||
out.put("sj", this.timedJavaStartup);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public JSONObject getCrashedJSON() throws JSONException {
|
||||
JSONObject out = new JSONObject();
|
||||
// We use ints here instead of booleans, because we're packing
|
||||
// stuff into JSON, and saving bytes in the DB is a worthwhile
|
||||
// goal.
|
||||
out.put("oom", this.wasOOM ? 1 : 0);
|
||||
out.put("stopped", this.wasStopped ? 1 : 0);
|
||||
out.put("r", "A");
|
||||
return out;
|
||||
}
|
||||
|
||||
public void setTimedGeckoStartup(final long duration) {
|
||||
timedGeckoStartup = duration;
|
||||
}
|
||||
|
||||
public void setTimedJavaStartup(final long duration) {
|
||||
timedJavaStartup = duration;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.health;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* StubbedHealthRecorder is an implementation of HealthRecorder that does (you guessed it!)
|
||||
* nothing.
|
||||
*/
|
||||
public class StubbedHealthRecorder implements HealthRecorder {
|
||||
public boolean isEnabled() { return false; }
|
||||
|
||||
public void setCurrentSession(SessionInformation session) { }
|
||||
public void checkForOrphanSessions() { }
|
||||
|
||||
public void recordGeckoStartupTime(long duration) { }
|
||||
public void recordJavaStartupTime(long duration) { }
|
||||
public void recordSearch(final String engineID, final String location) { }
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor) { }
|
||||
public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment) { }
|
||||
|
||||
public void onAppLocaleChanged(String to) { }
|
||||
public void onAddonChanged(String id, JSONObject json) { }
|
||||
public void onAddonUninstalling(String id) { }
|
||||
public void onEnvironmentChanged() { }
|
||||
public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) { }
|
||||
|
||||
public void close() { }
|
||||
}
|
|
@ -533,9 +533,9 @@ public class BrowserSearch extends HomeFragment
|
|||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Show suggestions opt-in prompt only if user hasn't been prompted
|
||||
// and we're not on a private browsing tab.
|
||||
if (!suggestionsPrompted && mSuggestClient != null) {
|
||||
// Show suggestions opt-in prompt only if suggestions are not enabled yet,
|
||||
// user hasn't been prompted and we're not on a private browsing tab.
|
||||
if (!mSuggestionsEnabled && !suggestionsPrompted && mSuggestClient != null) {
|
||||
showSuggestionsOptIn();
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
|
|
|
@ -168,7 +168,7 @@ class HomeAdapter extends FragmentStatePagerAdapter {
|
|||
args.putBoolean(HomePager.CAN_LOAD_ARG, mCanLoadHint);
|
||||
|
||||
// Only DynamicPanels need the PanelConfig argument
|
||||
if (mPanelConfig.getType() == PanelType.DYNAMIC) {
|
||||
if (mPanelConfig.isDynamic()) {
|
||||
args.putParcelable(HomePager.PANEL_CONFIG_ARG, mPanelConfig);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
@ -104,11 +106,18 @@ public final class HomeConfig {
|
|||
|
||||
public enum Flags {
|
||||
DEFAULT_PANEL,
|
||||
DISABLED_PANEL
|
||||
DISABLED_PANEL,
|
||||
DELETED_PANEL
|
||||
}
|
||||
|
||||
public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
|
||||
mType = PanelType.fromId(json.getString(JSON_KEY_TYPE));
|
||||
final String panelType = json.optString(JSON_KEY_TYPE, null);
|
||||
if (TextUtils.isEmpty(panelType)) {
|
||||
mType = PanelType.DYNAMIC;
|
||||
} else {
|
||||
mType = PanelType.fromId(panelType);
|
||||
}
|
||||
|
||||
mTitle = json.getString(JSON_KEY_TITLE);
|
||||
mId = json.getString(JSON_KEY_ID);
|
||||
|
||||
|
@ -249,6 +258,10 @@ public final class HomeConfig {
|
|||
return (mViews != null ? mViews.get(index) : null);
|
||||
}
|
||||
|
||||
public boolean isDynamic() {
|
||||
return (mType == PanelType.DYNAMIC);
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return mFlags.contains(Flags.DEFAULT_PANEL);
|
||||
}
|
||||
|
@ -273,6 +286,18 @@ public final class HomeConfig {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return mFlags.contains(Flags.DELETED_PANEL);
|
||||
}
|
||||
|
||||
public void setIsDeleted(boolean isDeleted) {
|
||||
if (isDeleted) {
|
||||
mFlags.add(Flags.DELETED_PANEL);
|
||||
} else {
|
||||
mFlags.remove(Flags.DELETED_PANEL);
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject toJSON() throws JSONException {
|
||||
final JSONObject json = new JSONObject();
|
||||
|
||||
|
@ -308,6 +333,24 @@ public final class HomeConfig {
|
|||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof PanelConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PanelConfig other = (PanelConfig) o;
|
||||
return mId.equals(other.mId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
@ -536,6 +579,12 @@ public final class HomeConfig {
|
|||
public void setOnChangeListener(OnChangeListener listener);
|
||||
}
|
||||
|
||||
// UUIDs used to create PanelConfigs for default built-in panels
|
||||
private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
|
||||
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
||||
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
||||
|
||||
private final HomeConfigBackend mBackend;
|
||||
|
||||
public HomeConfig(HomeConfigBackend backend) {
|
||||
|
@ -546,14 +595,56 @@ public final class HomeConfig {
|
|||
return mBackend.load();
|
||||
}
|
||||
|
||||
public void save(List<PanelConfig> entries) {
|
||||
mBackend.save(entries);
|
||||
public void save(List<PanelConfig> panelConfigs) {
|
||||
for (PanelConfig panelConfig : panelConfigs) {
|
||||
if (panelConfig.isDeleted()) {
|
||||
throw new IllegalArgumentException("Should never save a deleted PanelConfig: " + panelConfig.getId());
|
||||
}
|
||||
}
|
||||
|
||||
mBackend.save(panelConfigs);
|
||||
}
|
||||
|
||||
public void setOnChangeListener(OnChangeListener listener) {
|
||||
mBackend.setOnChangeListener(listener);
|
||||
}
|
||||
|
||||
public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {
|
||||
return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class));
|
||||
}
|
||||
|
||||
public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet<PanelConfig.Flags> flags) {
|
||||
int titleId = 0;
|
||||
String id = null;
|
||||
|
||||
switch(panelType) {
|
||||
case TOP_SITES:
|
||||
titleId = R.string.home_top_sites_title;
|
||||
id = TOP_SITES_PANEL_ID;
|
||||
break;
|
||||
|
||||
case BOOKMARKS:
|
||||
titleId = R.string.bookmarks_title;
|
||||
id = BOOKMARKS_PANEL_ID;
|
||||
break;
|
||||
|
||||
case HISTORY:
|
||||
titleId = R.string.home_history_title;
|
||||
id = HISTORY_PANEL_ID;
|
||||
break;
|
||||
|
||||
case READING_LIST:
|
||||
titleId = R.string.reading_list_title;
|
||||
id = READING_LIST_PANEL_ID;
|
||||
break;
|
||||
|
||||
case DYNAMIC:
|
||||
throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
|
||||
}
|
||||
|
||||
return new PanelConfig(panelType, context.getString(titleId), id, flags);
|
||||
}
|
||||
|
||||
public static HomeConfig getDefault(Context context) {
|
||||
return new HomeConfig(new HomeConfigPrefsBackend(context));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
||||
* 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/. */
|
||||
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelType;
|
||||
import org.mozilla.gecko.home.PanelManager.PanelInfo;
|
||||
import org.mozilla.gecko.home.PanelManager.RequestCallback;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class HomeConfigInvalidator implements GeckoEventListener {
|
||||
public static final String LOGTAG = "HomeConfigInvalidator";
|
||||
|
||||
private static final HomeConfigInvalidator sInstance = new HomeConfigInvalidator();
|
||||
|
||||
private static final int INVALIDATION_DELAY_MSEC = 500;
|
||||
private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
|
||||
|
||||
private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
|
||||
private static final String EVENT_HOMEPANELS_REMOVE = "HomePanels:Remove";
|
||||
|
||||
private static final String JSON_KEY_PANEL = "panel";
|
||||
|
||||
private Context mContext;
|
||||
private HomeConfig mHomeConfig;
|
||||
|
||||
private final ConcurrentLinkedQueue<PanelConfig> mPendingChanges = new ConcurrentLinkedQueue<PanelConfig>();
|
||||
private final Runnable mInvalidationRunnable = new InvalidationRunnable();
|
||||
|
||||
public static HomeConfigInvalidator getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void init(Context context) {
|
||||
mContext = context;
|
||||
mHomeConfig = HomeConfig.getDefault(context);
|
||||
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_INSTALL, this);
|
||||
GeckoAppShell.getEventDispatcher().registerEventListener(EVENT_HOMEPANELS_REMOVE, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(String event, JSONObject message) {
|
||||
try {
|
||||
final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
|
||||
final PanelConfig panelConfig = new PanelConfig(json);
|
||||
|
||||
if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
|
||||
handlePanelInstall(panelConfig);
|
||||
} else if (event.equals(EVENT_HOMEPANELS_REMOVE)) {
|
||||
Log.d(LOGTAG, EVENT_HOMEPANELS_REMOVE);
|
||||
handlePanelRemove(panelConfig);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Failed to handle event " + event, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
private void handlePanelInstall(PanelConfig panelConfig) {
|
||||
mPendingChanges.offer(panelConfig);
|
||||
Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko thread.
|
||||
*/
|
||||
private void handlePanelRemove(PanelConfig panelConfig) {
|
||||
panelConfig.setIsDeleted(true);
|
||||
mPendingChanges.offer(panelConfig);
|
||||
Log.d(LOGTAG, "handlePanelRemove: " + mPendingChanges.size());
|
||||
|
||||
scheduleInvalidation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the gecko or main thread.
|
||||
*/
|
||||
private void scheduleInvalidation() {
|
||||
final Handler handler = ThreadUtils.getBackgroundHandler();
|
||||
|
||||
handler.removeCallbacks(mInvalidationRunnable);
|
||||
handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
|
||||
|
||||
Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> executePendingChanges(List<PanelConfig> panelConfigs) {
|
||||
while (!mPendingChanges.isEmpty()) {
|
||||
final PanelConfig panelConfig = mPendingChanges.poll();
|
||||
final String id = panelConfig.getId();
|
||||
|
||||
if (panelConfig.isDeleted()) {
|
||||
if (panelConfigs.remove(panelConfig)) {
|
||||
Log.d(LOGTAG, "executePendingChanges: removed panel " + id);
|
||||
}
|
||||
} else {
|
||||
final int index = panelConfigs.indexOf(panelConfig);
|
||||
if (index >= 0) {
|
||||
panelConfigs.set(index, panelConfig);
|
||||
Log.d(LOGTAG, "executePendingChanges: replaced position " + index + " with " + id);
|
||||
} else {
|
||||
panelConfigs.add(panelConfig);
|
||||
Log.d(LOGTAG, "executePendingChanges: added panel " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return executeRefresh(panelConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> refreshFromPanelInfos(List<PanelConfig> panelConfigs, List<PanelInfo> panelInfos) {
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos");
|
||||
|
||||
final int count = panelConfigs.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final PanelConfig panelConfig = panelConfigs.get(i);
|
||||
|
||||
PanelConfig refreshedPanelConfig = null;
|
||||
if (panelConfig.isDynamic()) {
|
||||
for (PanelInfo panelInfo : panelInfos) {
|
||||
if (panelInfo.getId().equals(panelConfig.getId())) {
|
||||
refreshedPanelConfig = panelInfo.toPanelConfig();
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: refreshing from panel info: " + panelInfo.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshedPanelConfig = createBuiltinPanelConfig(mContext, panelConfig.getType());
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: refreshing built-in panel: " + panelConfig.getId());
|
||||
}
|
||||
|
||||
if (refreshedPanelConfig == null) {
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId());
|
||||
refreshedPanelConfig = panelConfig;
|
||||
}
|
||||
|
||||
refreshedPanelConfig.setIsDefault(panelConfig.isDefault());
|
||||
refreshedPanelConfig.setIsDisabled(panelConfig.isDisabled());
|
||||
|
||||
Log.d(LOGTAG, "refreshFromPanelInfos: set " + i + " with " + refreshedPanelConfig.getId());
|
||||
panelConfigs.set(i, refreshedPanelConfig);
|
||||
}
|
||||
|
||||
return panelConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private List<PanelConfig> executeRefresh(List<PanelConfig> panelConfigs) {
|
||||
if (panelConfigs.isEmpty()) {
|
||||
return panelConfigs;
|
||||
}
|
||||
|
||||
Log.d(LOGTAG, "executeRefresh");
|
||||
|
||||
final Set<String> ids = new HashSet<String>();
|
||||
for (PanelConfig panelConfig : panelConfigs) {
|
||||
ids.add(panelConfig.getId());
|
||||
}
|
||||
|
||||
final Object panelRequestLock = new Object();
|
||||
final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
|
||||
|
||||
final PanelManager pm = new PanelManager();
|
||||
pm.requestPanelsById(ids, new RequestCallback() {
|
||||
@Override
|
||||
public void onComplete(List<PanelInfo> panelInfos) {
|
||||
synchronized(panelRequestLock) {
|
||||
latestPanelInfos.addAll(panelInfos);
|
||||
Log.d(LOGTAG, "executeRefresh: fetched panel infos: " + panelInfos.size());
|
||||
|
||||
panelRequestLock.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized(panelRequestLock) {
|
||||
panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC);
|
||||
|
||||
Log.d(LOGTAG, "executeRefresh: done fetching panel infos");
|
||||
return refreshFromPanelInfos(panelConfigs, latestPanelInfos);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(LOGTAG, "Failed to fetch panels from gecko", e);
|
||||
return panelConfigs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs in the background thread.
|
||||
*/
|
||||
private class InvalidationRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
mHomeConfig.save(executePendingChanges(mHomeConfig.load()));
|
||||
}
|
||||
};
|
||||
}
|
|
@ -13,6 +13,8 @@ import org.mozilla.gecko.home.HomeConfig.PanelType;
|
|||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
@ -25,7 +27,6 @@ import android.text.TextUtils;
|
|||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -34,12 +35,6 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
|||
|
||||
private static final String PREFS_KEY = "home_panels";
|
||||
|
||||
// UUIDs used to create PanelConfigs for default built-in panels
|
||||
private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
|
||||
private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
|
||||
private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
|
||||
private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
|
||||
|
||||
private final Context mContext;
|
||||
private PrefsListener mPrefsListener;
|
||||
private OnChangeListener mChangeListener;
|
||||
|
@ -55,26 +50,18 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
|||
private List<PanelConfig> loadDefaultConfig() {
|
||||
final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
|
||||
|
||||
panelConfigs.add(new PanelConfig(PanelType.TOP_SITES,
|
||||
mContext.getString(R.string.home_top_sites_title),
|
||||
TOP_SITES_PANEL_ID,
|
||||
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.TOP_SITES,
|
||||
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
|
||||
|
||||
panelConfigs.add(new PanelConfig(PanelType.BOOKMARKS,
|
||||
mContext.getString(R.string.bookmarks_title),
|
||||
BOOKMARKS_PANEL_ID));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
|
||||
|
||||
// We disable reader mode support on low memory devices. Hence the
|
||||
// reading list panel should not show up on such devices.
|
||||
if (!HardwareUtils.isLowMemoryPlatform()) {
|
||||
panelConfigs.add(new PanelConfig(PanelType.READING_LIST,
|
||||
mContext.getString(R.string.reading_list_title),
|
||||
READING_LIST_PANEL_ID));
|
||||
panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
|
||||
}
|
||||
|
||||
final PanelConfig historyEntry = new PanelConfig(PanelType.HISTORY,
|
||||
mContext.getString(R.string.home_history_title),
|
||||
HISTORY_PANEL_ID);
|
||||
final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
|
||||
|
||||
// On tablets, the history panel is the last.
|
||||
// On phones, the history panel is the first one.
|
||||
|
@ -126,7 +113,7 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
|
|||
panelConfigs = loadConfigFromString(jsonString);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(panelConfigs);
|
||||
return panelConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.db.BrowserContract.HomeItems;
|
||||
import org.mozilla.gecko.home.HomeConfig.ViewConfig;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
|
||||
import org.mozilla.gecko.home.PanelLayout.PanelView;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
@ -15,18 +18,30 @@ import android.support.v4.widget.CursorAdapter;
|
|||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
|
||||
public class PanelGridView extends GridView implements DatasetBacked {
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class PanelGridView extends GridView
|
||||
implements DatasetBacked, PanelView {
|
||||
private static final String LOGTAG = "GeckoPanelGridView";
|
||||
|
||||
private final PanelGridViewAdapter mAdapter;
|
||||
protected OnUrlOpenListener mUrlOpenListener;
|
||||
|
||||
public PanelGridView(Context context, ViewConfig viewConfig) {
|
||||
super(context, null, R.attr.panelGridViewStyle);
|
||||
mAdapter = new PanelGridViewAdapter(context);
|
||||
setAdapter(mAdapter);
|
||||
setNumColumns(AUTO_FIT);
|
||||
setOnItemClickListener(new PanelGridItemClickListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mUrlOpenListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -34,6 +49,11 @@ public class PanelGridView extends GridView implements DatasetBacked {
|
|||
mAdapter.swapCursor(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnUrlOpenListener(OnUrlOpenListener listener) {
|
||||
mUrlOpenListener = listener;
|
||||
}
|
||||
|
||||
private class PanelGridViewAdapter extends CursorAdapter {
|
||||
|
||||
public PanelGridViewAdapter(Context context) {
|
||||
|
@ -51,4 +71,19 @@ public class PanelGridView extends GridView implements DatasetBacked {
|
|||
return new PanelGridItemView(context);
|
||||
}
|
||||
}
|
||||
|
||||
private class PanelGridItemClickListener implements AdapterView.OnItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Cursor cursor = mAdapter.getCursor();
|
||||
if (cursor == null || !cursor.moveToPosition(position)) {
|
||||
throw new IllegalStateException("Couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
|
||||
final String url = cursor.getString(urlIndex);
|
||||
|
||||
mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.OPEN_WITH_INTENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
|
|||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
|
@ -23,22 +24,38 @@ import android.util.SparseArray;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PanelManager implements GeckoEventListener {
|
||||
private static final String LOGTAG = "GeckoPanelManager";
|
||||
|
||||
public class PanelInfo {
|
||||
public final String id;
|
||||
public final String title;
|
||||
public final String layout;
|
||||
public final JSONArray views;
|
||||
private final String mId;
|
||||
private final String mTitle;
|
||||
private final JSONObject mJSONData;
|
||||
|
||||
public PanelInfo(String id, String title, String layout, JSONArray views) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.layout = layout;
|
||||
this.views = views;
|
||||
public PanelInfo(String id, String title, JSONObject jsonData) {
|
||||
mId = id;
|
||||
mTitle = title;
|
||||
mJSONData = jsonData;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public PanelConfig toPanelConfig() {
|
||||
try {
|
||||
return new PanelConfig(mJSONData);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "Failed to convert PanelInfo to PanelConfig", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,11 +69,14 @@ public class PanelManager implements GeckoEventListener {
|
|||
private static final SparseArray<RequestCallback> sCallbacks = new SparseArray<RequestCallback>();
|
||||
|
||||
/**
|
||||
* Asynchronously fetches list of available panels from Gecko.
|
||||
* Asynchronously fetches list of available panels from Gecko
|
||||
* for the given IDs.
|
||||
*
|
||||
* @param ids list of panel ids to be fetched. A null value will fetch all
|
||||
* available panels.
|
||||
* @param callback onComplete will be called on the UI thread.
|
||||
*/
|
||||
public void requestAvailablePanels(RequestCallback callback) {
|
||||
public void requestPanelsById(Set<String> ids, RequestCallback callback) {
|
||||
final int requestId = sRequestId.getAndIncrement();
|
||||
|
||||
synchronized(sCallbacks) {
|
||||
|
@ -67,7 +87,33 @@ public class PanelManager implements GeckoEventListener {
|
|||
sCallbacks.put(requestId, callback);
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomePanels:Get", Integer.toString(requestId)));
|
||||
final JSONObject message = new JSONObject();
|
||||
try {
|
||||
message.put("requestId", requestId);
|
||||
|
||||
if (ids != null && ids.size() > 0) {
|
||||
JSONArray idsArray = new JSONArray();
|
||||
for (String id : ids) {
|
||||
idsArray.put(id);
|
||||
}
|
||||
|
||||
message.put("ids", idsArray);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Failed to build event to request panels by id", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomePanels:Get", message.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously fetches list of available panels from Gecko.
|
||||
*
|
||||
* @param callback onComplete will be called on the UI thread.
|
||||
*/
|
||||
public void requestAvailablePanels(RequestCallback callback) {
|
||||
requestPanelsById(null, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,9 +158,7 @@ public class PanelManager implements GeckoEventListener {
|
|||
private PanelInfo getPanelInfoFromJSON(JSONObject jsonPanelInfo) throws JSONException {
|
||||
final String id = jsonPanelInfo.getString("id");
|
||||
final String title = jsonPanelInfo.getString("title");
|
||||
final String layout = jsonPanelInfo.getString("layout");
|
||||
final JSONArray views = jsonPanelInfo.getJSONArray("views");
|
||||
|
||||
return new PanelInfo(id, title, layout, views);
|
||||
return new PanelInfo(id, title, jsonPanelInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -207,6 +207,9 @@ gbjar.sources += [
|
|||
'GlobalHistory.java',
|
||||
'health/BrowserHealthRecorder.java',
|
||||
'health/BrowserHealthReporter.java',
|
||||
'health/HealthRecorder.java',
|
||||
'health/SessionInformation.java',
|
||||
'health/StubbedHealthRecorder.java',
|
||||
'home/BookmarkFolderView.java',
|
||||
'home/BookmarksListAdapter.java',
|
||||
'home/BookmarksListView.java',
|
||||
|
@ -219,6 +222,7 @@ gbjar.sources += [
|
|||
'home/HomeAdapter.java',
|
||||
'home/HomeBanner.java',
|
||||
'home/HomeConfig.java',
|
||||
'home/HomeConfigInvalidator.java',
|
||||
'home/HomeConfigLoader.java',
|
||||
'home/HomeConfigPrefsBackend.java',
|
||||
'home/HomeContextMenuInfo.java',
|
||||
|
|
|
@ -24,7 +24,7 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
|||
public static final String LOGTAG = "PanelsPrefCategory";
|
||||
|
||||
protected HomeConfig mHomeConfig;
|
||||
protected final List<PanelConfig> mPanelConfigs = new ArrayList<PanelConfig>();
|
||||
protected List<PanelConfig> mPanelConfigs;
|
||||
|
||||
protected UiAsyncTask<Void, Void, List<PanelConfig>> mLoadTask;
|
||||
protected UiAsyncTask<Void, Void, Void> mSaveTask;
|
||||
|
@ -67,17 +67,15 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
|||
|
||||
@Override
|
||||
public void onPostExecute(List<PanelConfig> panelConfigs) {
|
||||
displayPanelConfig(panelConfigs);
|
||||
mPanelConfigs = panelConfigs;
|
||||
displayPanelConfig();
|
||||
}
|
||||
};
|
||||
mLoadTask.execute();
|
||||
}
|
||||
|
||||
private void displayPanelConfig(List<PanelConfig> panelConfigs) {
|
||||
for (PanelConfig panelConfig: panelConfigs) {
|
||||
// Populate our local copy of the panels.
|
||||
mPanelConfigs.add(panelConfig);
|
||||
|
||||
private void displayPanelConfig() {
|
||||
for (PanelConfig panelConfig : mPanelConfigs) {
|
||||
// Create and add the pref.
|
||||
final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this);
|
||||
pref.setTitle(panelConfig.getTitle());
|
||||
|
@ -102,6 +100,10 @@ public class PanelsPreferenceCategory extends CustomListCategory {
|
|||
* @param panelConfigs Configuration to be saved
|
||||
*/
|
||||
private void saveHomeConfig() {
|
||||
if (mPanelConfigs == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<PanelConfig> panelConfigs = makeConfigListDeepCopy();
|
||||
mSaveTask = new UiAsyncTask<Void, Void, Void>(ThreadUtils.getBackgroundHandler()) {
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,7 @@ const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
|
|||
const XRE_UPDATE_ROOT_DIR = "UpdRootD";
|
||||
const ENVVAR_UPDATE_DIR = "UPDATES_DIRECTORY";
|
||||
const WEBAPPS_DIR = "webappsDir";
|
||||
const DOWNLOAD_DIR = "DfltDwnld"
|
||||
|
||||
const SYSTEM_DIST_PATH = "/system/@ANDROID_PACKAGE_NAME@/distribution";
|
||||
|
||||
|
@ -73,6 +74,9 @@ DirectoryProvider.prototype = {
|
|||
}
|
||||
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
return dm.defaultDownloadsDirectory;
|
||||
} else if (prop == DOWNLOAD_DIR) {
|
||||
let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
|
||||
return dm.defaultDownloadsDirectory;
|
||||
}
|
||||
|
||||
// We are retuning null to show failure instead for throwing an error. The
|
||||
|
|
|
@ -23,6 +23,7 @@ FilePicker.prototype = {
|
|||
_filePath: null,
|
||||
_promptActive: false,
|
||||
_filterIndex: 0,
|
||||
_addToRecentDocs: false,
|
||||
|
||||
init: function(aParent, aTitle, aMode) {
|
||||
this._domWin = aParent;
|
||||
|
@ -150,11 +151,11 @@ FilePicker.prototype = {
|
|||
},
|
||||
|
||||
get addToRecentDocs() {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
return this._addToRecentDocs;
|
||||
},
|
||||
|
||||
set addToRecentDocs(val) {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
this._addToRecentDocs = val;
|
||||
},
|
||||
|
||||
get mode() {
|
||||
|
|
|
@ -585,7 +585,6 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DLL_SUFFIX@
|
|||
|
||||
@BINPATH@/components/Payment.js
|
||||
@BINPATH@/components/PaymentFlowInfo.js
|
||||
@BINPATH@/components/PaymentRequestInfo.js
|
||||
@BINPATH@/components/Payment.manifest
|
||||
@BINPATH@/components/PaymentsUI.js
|
||||
|
||||
|
|
|
@ -166,16 +166,27 @@ let HomePanels = {
|
|||
// Holds the currrent set of registered panels.
|
||||
_panels: {},
|
||||
|
||||
_handleGet: function(requestId) {
|
||||
_panelToJSON : function(panel) {
|
||||
return {
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
layout: panel.layout,
|
||||
views: panel.views
|
||||
};
|
||||
},
|
||||
|
||||
_handleGet: function(data) {
|
||||
let requestId = data.requestId;
|
||||
let ids = data.ids || null;
|
||||
|
||||
let panels = [];
|
||||
for (let id in this._panels) {
|
||||
let panel = this._panels[id];
|
||||
panels.push({
|
||||
id: panel.id,
|
||||
title: panel.title,
|
||||
layout: panel.layout,
|
||||
views: panel.views
|
||||
});
|
||||
|
||||
// Null ids means we want to fetch all available panels
|
||||
if (ids == null || ids.indexOf(panel.id) >= 0) {
|
||||
panels.push(this._panelToJSON(panel));
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
|
@ -211,14 +222,26 @@ let HomePanels = {
|
|||
}
|
||||
|
||||
this._panels[panel.id] = panel;
|
||||
|
||||
if (options.autoInstall) {
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Install",
|
||||
panel: this._panelToJSON(panel)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
remove: function(id) {
|
||||
if (!(id in this._panels)) {
|
||||
throw "Home.panels: Panel doesn't exist: id = " + id;
|
||||
}
|
||||
|
||||
let panel = this._panels[id];
|
||||
delete this._panels[id];
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:Remove",
|
||||
id: id
|
||||
panel: this._panelToJSON(panel)
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -242,7 +265,7 @@ this.Home = {
|
|||
observe: function(subject, topic, data) {
|
||||
switch(topic) {
|
||||
case "HomePanels:Get":
|
||||
HomePanels._handleGet(data);
|
||||
HomePanels._handleGet(JSON.parse(data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -314,7 +314,7 @@ void ARTSPConnection::onConnect(const sp<AMessage> &msg) {
|
|||
mState = CONNECTED;
|
||||
mNextCSeq = 1;
|
||||
|
||||
postReceiveReponseEvent();
|
||||
postReceiveResponseEvent();
|
||||
}
|
||||
|
||||
reply->post();
|
||||
|
@ -416,7 +416,7 @@ void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) {
|
|||
mState = CONNECTED;
|
||||
mNextCSeq = 1;
|
||||
|
||||
postReceiveReponseEvent();
|
||||
postReceiveResponseEvent();
|
||||
}
|
||||
|
||||
reply->post();
|
||||
|
@ -511,7 +511,7 @@ void ARTSPConnection::onReceiveResponse() {
|
|||
if (res == 1) {
|
||||
MakeSocketBlocking(mSocket, true);
|
||||
|
||||
bool success = receiveRTSPReponse();
|
||||
bool success = receiveRTSPResponse();
|
||||
|
||||
MakeSocketBlocking(mSocket, false);
|
||||
|
||||
|
@ -522,7 +522,7 @@ void ARTSPConnection::onReceiveResponse() {
|
|||
}
|
||||
}
|
||||
|
||||
postReceiveReponseEvent();
|
||||
postReceiveResponseEvent();
|
||||
}
|
||||
|
||||
void ARTSPConnection::flushPendingRequests() {
|
||||
|
@ -536,7 +536,7 @@ void ARTSPConnection::flushPendingRequests() {
|
|||
mPendingRequests.clear();
|
||||
}
|
||||
|
||||
void ARTSPConnection::postReceiveReponseEvent() {
|
||||
void ARTSPConnection::postReceiveResponseEvent() {
|
||||
if (mReceiveResponseEventPending) {
|
||||
return;
|
||||
}
|
||||
|
@ -621,7 +621,7 @@ static bool IsRTSPVersion(const AString &s) {
|
|||
return s == "RTSP/1.0";
|
||||
}
|
||||
|
||||
bool ARTSPConnection::receiveRTSPReponse() {
|
||||
bool ARTSPConnection::receiveRTSPResponse() {
|
||||
AString statusLine;
|
||||
|
||||
if (!receiveLine(&statusLine)) {
|
||||
|
|
|
@ -102,10 +102,10 @@ private:
|
|||
void onReceiveResponse();
|
||||
|
||||
void flushPendingRequests();
|
||||
void postReceiveReponseEvent();
|
||||
void postReceiveResponseEvent();
|
||||
|
||||
// Return false iff something went unrecoverably wrong.
|
||||
bool receiveRTSPReponse();
|
||||
bool receiveRTSPResponse();
|
||||
status_t receive(void *data, size_t size);
|
||||
bool receiveLine(AString *line);
|
||||
sp<ABuffer> receiveBinaryData();
|
||||
|
|
|
@ -9,6 +9,7 @@ const Cu = Components.utils;
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
const SYNC_PREFS_BRANCH = "services.sync.";
|
||||
|
@ -25,13 +26,25 @@ const SYNC_PREFS_BRANCH = "services.sync.";
|
|||
*
|
||||
* If Sync is not configured, no extra Sync code is loaded. If an
|
||||
* external component (say the UI) needs to interact with Sync, it
|
||||
* should do something like the following:
|
||||
* should use the promise-base function whenLoaded() - something like the
|
||||
* following:
|
||||
*
|
||||
* // 1. Grab a handle to the Sync XPCOM service.
|
||||
* let service = Cc["@mozilla.org/weave/service;1"]
|
||||
* .getService(Components.interfaces.nsISupports)
|
||||
* .wrappedJSObject;
|
||||
*
|
||||
* // 2. Use the .then method of the promise.
|
||||
* service.whenLoaded().then(() => {
|
||||
* // You are free to interact with "Weave." objects.
|
||||
* return;
|
||||
* });
|
||||
*
|
||||
* And that's it! However, if you really want to avoid promises and do it
|
||||
* old-school, then
|
||||
*
|
||||
* // 1. Get a reference to the service as done in (1) above.
|
||||
*
|
||||
* // 2. Check if the service has been initialized.
|
||||
* if (service.ready) {
|
||||
* // You are free to interact with "Weave." objects.
|
||||
|
@ -65,6 +78,20 @@ WeaveService.prototype = {
|
|||
Weave.Service;
|
||||
},
|
||||
|
||||
whenLoaded: function() {
|
||||
if (this.ready) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
let deferred = Promise.defer();
|
||||
|
||||
Services.obs.addObserver(function onReady() {
|
||||
Services.obs.removeObserver(onReady, "weave:service:ready");
|
||||
deferred.resolve();
|
||||
}, "weave:service:ready", false);
|
||||
this.ensureLoaded();
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
get fxAccountsEnabled() {
|
||||
// work out what identity manager to use. This is stored in a preference;
|
||||
// if the preference exists, we trust it.
|
||||
|
|
|
@ -164,6 +164,7 @@ this.BrowserIDManager.prototype = {
|
|||
},
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
this._log.debug("observed " + topic);
|
||||
switch (topic) {
|
||||
case fxAccountsCommon.ONLOGIN_NOTIFICATION:
|
||||
this.initializeWithCurrentIdentity(true);
|
||||
|
@ -409,15 +410,17 @@ this.BrowserIDManager.prototype = {
|
|||
// Both Jelly and FxAccounts give us kB as hex
|
||||
let kBbytes = CommonUtils.hexToBytes(userData.kB);
|
||||
let headers = {"X-Client-State": this._computeXClientState(kBbytes)};
|
||||
log.info("Fetching Sync token from: " + tokenServerURI);
|
||||
log.info("Fetching assertion and token from: " + tokenServerURI);
|
||||
|
||||
function getToken(tokenServerURI, assertion) {
|
||||
log.debug("Getting a token");
|
||||
let deferred = Promise.defer();
|
||||
let cb = function (err, token) {
|
||||
if (err) {
|
||||
log.info("TokenServerClient.getTokenFromBrowserIDAssertion() failed with: " + err.message);
|
||||
return deferred.reject(new AuthenticationError(err.message));
|
||||
} else {
|
||||
log.debug("Successfully got a sync token");
|
||||
return deferred.resolve(token);
|
||||
}
|
||||
};
|
||||
|
@ -427,6 +430,7 @@ this.BrowserIDManager.prototype = {
|
|||
}
|
||||
|
||||
function getAssertion() {
|
||||
log.debug("Getting an assertion");
|
||||
let audience = Services.io.newURI(tokenServerURI, null, null).prePath;
|
||||
return fxAccounts.getAssertion(audience).then(null, err => {
|
||||
if (err.code === 401) {
|
||||
|
|
|
@ -359,6 +359,7 @@
|
|||
"dom/tests/mochitest/sessionstorage/test_cookieSession.html":"4 failures",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html":"no storage chrome event received",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageBaseSessionOnly.html":"bug 967737",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageClone.html":"bug 968051",
|
||||
"dom/tests/mochitest/sessionstorage/test_sessionStorageHttpHttps.html":"needs https to work",
|
||||
|
||||
"dom/tests/mochitest/pointerlock/test_pointerlock-api.html":"window.open focus issues (using fullscreen)",
|
||||
|
|
|
@ -1844,26 +1844,39 @@ function appendSectionHeader(aP, aText)
|
|||
function saveReportsToFile()
|
||||
{
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter("Zipped JSON files", "*.json.gz");
|
||||
fp.appendFilters(Ci.nsIFilePicker.filterAll);
|
||||
fp.filterIndex = 0;
|
||||
fp.addToRecentDocs = true;
|
||||
fp.defaultString = "memory-report.json.gz";
|
||||
|
||||
let fpFinish = function(file) {
|
||||
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
|
||||
.getService(Ci.nsIMemoryInfoDumper);
|
||||
|
||||
let finishDumping = () => {
|
||||
updateMainAndFooter("Saved reports to " + file.path, HIDE_FOOTER);
|
||||
}
|
||||
|
||||
dumper.dumpMemoryReportsToNamedFile(file.path, finishDumping, null);
|
||||
}
|
||||
|
||||
let fpCallback = function(aResult) {
|
||||
if (aResult == Ci.nsIFilePicker.returnOK ||
|
||||
aResult == Ci.nsIFilePicker.returnReplace) {
|
||||
|
||||
let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
|
||||
.getService(Ci.nsIMemoryInfoDumper);
|
||||
|
||||
let finishDumping = () => {
|
||||
updateMainAndFooter("Saved reports to " + fp.file.path, HIDE_FOOTER);
|
||||
}
|
||||
|
||||
dumper.dumpMemoryReportsToNamedFile(fp.file.path, finishDumping, null);
|
||||
fpFinish(fp.file);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
|
||||
} catch(ex) {
|
||||
// This will fail on Android, since there is no Save as file picker there.
|
||||
// Just save to the default downloads dir if it does.
|
||||
let file = Services.dirsvc.get("DfltDwnld", Ci.nsIFile);
|
||||
file.append(fp.defaultString);
|
||||
fpFinish(file);
|
||||
return;
|
||||
}
|
||||
fp.open(fpCallback);
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
</stack>
|
||||
|
||||
<panel id="panel" type="arrow" onpopupshown="checkPanelPosition(this)" onpopuphidden="runNextTest.next()">
|
||||
<label id="panellabel" value="This is some text." height="65"/>
|
||||
<label id="panellabel" value="This is some text..." height="65"/>
|
||||
</panel>
|
||||
|
||||
<panel id="bigpanel" type="arrow" onpopupshown="checkBigPanel(this)" onpopuphidden="runNextTest.next()">
|
||||
<button label="This is some text." height="3000"/>
|
||||
<button label="This is some text..." height="3000"/>
|
||||
</panel>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
// [dammit]: acorn_loose.js
|
||||
// [walk]: util/walk.js
|
||||
|
||||
(function(mod) {
|
||||
(function(root, mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS
|
||||
if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD
|
||||
mod(self.acorn || (self.acorn = {})); // Plain browser env
|
||||
})(function(exports) {
|
||||
mod(root.acorn || (root.acorn = {})); // Plain browser env
|
||||
})(this, function(exports) {
|
||||
"use strict";
|
||||
|
||||
exports.version = "0.1.01";
|
||||
exports.version = "0.4.1";
|
||||
|
||||
// The main exported interface (under `self.acorn` when in the
|
||||
// browser) is a `parse` function that takes a code string and
|
||||
|
@ -77,7 +77,8 @@
|
|||
// character offsets that denote the start and end of the comment.
|
||||
// When the `locations` option is on, two more parameters are
|
||||
// passed, the full `{line, column}` locations of the start and
|
||||
// end of the comments.
|
||||
// end of the comments. Note that you are not allowed to call the
|
||||
// parser from the callback—that will corrupt its internal state.
|
||||
onComment: null,
|
||||
// Nodes have their start and end characters offsets recorded in
|
||||
// `start` and `end` properties (directly on the node, rather than
|
||||
|
@ -94,14 +95,17 @@
|
|||
// toplevel forms of the parsed file to the `Program` (top) node
|
||||
// of an existing parse tree.
|
||||
program: null,
|
||||
// When `location` is on, you can pass this to record the source
|
||||
// When `locations` is on, you can pass this to record the source
|
||||
// file in every node's `loc` object.
|
||||
sourceFile: null
|
||||
sourceFile: null,
|
||||
// This value, if given, is stored in every node, whether
|
||||
// `locations` is on or off.
|
||||
directSourceFile: null
|
||||
};
|
||||
|
||||
function setOptions(opts) {
|
||||
options = opts || {};
|
||||
for (var opt in defaultOptions) if (!options.hasOwnProperty(opt))
|
||||
for (var opt in defaultOptions) if (!Object.prototype.hasOwnProperty.call(options, opt))
|
||||
options[opt] = defaultOptions[opt];
|
||||
sourceFile = options.sourceFile || null;
|
||||
}
|
||||
|
@ -138,6 +142,7 @@
|
|||
|
||||
var t = {};
|
||||
function getToken(forceRegexp) {
|
||||
lastEnd = tokEnd;
|
||||
readToken(forceRegexp);
|
||||
t.start = tokStart; t.end = tokEnd;
|
||||
t.startLoc = tokStartLoc; t.endLoc = tokEndLoc;
|
||||
|
@ -147,14 +152,14 @@
|
|||
getToken.jumpTo = function(pos, reAllowed) {
|
||||
tokPos = pos;
|
||||
if (options.locations) {
|
||||
tokCurLine = tokLineStart = lineBreak.lastIndex = 0;
|
||||
tokCurLine = 1;
|
||||
tokLineStart = lineBreak.lastIndex = 0;
|
||||
var match;
|
||||
while ((match = lineBreak.exec(input)) && match.index < pos) {
|
||||
++tokCurLine;
|
||||
tokLineStart = match.index + match[0].length;
|
||||
}
|
||||
}
|
||||
var ch = input.charAt(pos - 1);
|
||||
tokRegexpAllowed = reAllowed;
|
||||
skipSpace();
|
||||
};
|
||||
|
@ -228,6 +233,10 @@
|
|||
throw err;
|
||||
}
|
||||
|
||||
// Reused empty array added for node fields that are always empty.
|
||||
|
||||
var empty = [];
|
||||
|
||||
// ## Token types
|
||||
|
||||
// The assignment of fine-grained, information-carrying type objects
|
||||
|
@ -313,13 +322,18 @@
|
|||
// in AssignmentExpression nodes.
|
||||
|
||||
var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true};
|
||||
var _assign = {isAssign: true, beforeExpr: true}, _plusmin = {binop: 9, prefix: true, beforeExpr: true};
|
||||
var _incdec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
|
||||
var _bin1 = {binop: 1, beforeExpr: true}, _bin2 = {binop: 2, beforeExpr: true};
|
||||
var _bin3 = {binop: 3, beforeExpr: true}, _bin4 = {binop: 4, beforeExpr: true};
|
||||
var _bin5 = {binop: 5, beforeExpr: true}, _bin6 = {binop: 6, beforeExpr: true};
|
||||
var _bin7 = {binop: 7, beforeExpr: true}, _bin8 = {binop: 8, beforeExpr: true};
|
||||
var _bin10 = {binop: 10, beforeExpr: true};
|
||||
var _assign = {isAssign: true, beforeExpr: true};
|
||||
var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true};
|
||||
var _logicalOR = {binop: 1, beforeExpr: true};
|
||||
var _logicalAND = {binop: 2, beforeExpr: true};
|
||||
var _bitwiseOR = {binop: 3, beforeExpr: true};
|
||||
var _bitwiseXOR = {binop: 4, beforeExpr: true};
|
||||
var _bitwiseAND = {binop: 5, beforeExpr: true};
|
||||
var _equality = {binop: 6, beforeExpr: true};
|
||||
var _relational = {binop: 7, beforeExpr: true};
|
||||
var _bitShift = {binop: 8, beforeExpr: true};
|
||||
var _plusMin = {binop: 9, prefix: true, beforeExpr: true};
|
||||
var _multiplyModulo = {binop: 10, beforeExpr: true};
|
||||
|
||||
// Provide access to the token types for external users of the
|
||||
// tokenizer.
|
||||
|
@ -328,7 +342,7 @@
|
|||
parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon,
|
||||
dot: _dot, question: _question, slash: _slash, eq: _eq, name: _name, eof: _eof,
|
||||
num: _num, regexp: _regexp, string: _string};
|
||||
for (var kw in keywordTypes) exports.tokTypes[kw] = keywordTypes[kw];
|
||||
for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw];
|
||||
|
||||
// This is a trick taken from Esprima. It turns out that, on
|
||||
// non-Chrome browsers, to check whether a string is in a set, a
|
||||
|
@ -405,9 +419,9 @@
|
|||
// are only applied when a character is found to actually have a
|
||||
// code point above 128.
|
||||
|
||||
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/;
|
||||
var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
|
||||
var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
|
||||
var nonASCIIidentifierChars = "\u0371-\u0374\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
|
||||
var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f";
|
||||
var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
|
||||
var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
|
||||
|
||||
|
@ -422,17 +436,17 @@
|
|||
|
||||
// Test whether a given character code starts an identifier.
|
||||
|
||||
function isIdentifierStart(code) {
|
||||
var isIdentifierStart = exports.isIdentifierStart = function(code) {
|
||||
if (code < 65) return code === 36;
|
||||
if (code < 91) return true;
|
||||
if (code < 97) return code === 95;
|
||||
if (code < 123)return true;
|
||||
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
|
||||
}
|
||||
};
|
||||
|
||||
// Test whether a given character is part of an identifier.
|
||||
|
||||
function isIdentifierChar(code) {
|
||||
var isIdentifierChar = exports.isIdentifierChar = function(code) {
|
||||
if (code < 48) return code === 36;
|
||||
if (code < 58) return true;
|
||||
if (code < 65) return false;
|
||||
|
@ -440,7 +454,7 @@
|
|||
if (code < 97) return code === 95;
|
||||
if (code < 123)return true;
|
||||
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
|
||||
}
|
||||
};
|
||||
|
||||
// ## Tokenizer
|
||||
|
||||
|
@ -496,7 +510,7 @@
|
|||
var start = tokPos;
|
||||
var startLoc = options.onComment && options.locations && new line_loc_t;
|
||||
var ch = input.charCodeAt(tokPos+=2);
|
||||
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8329) {
|
||||
while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
|
||||
++tokPos;
|
||||
ch = input.charCodeAt(tokPos);
|
||||
}
|
||||
|
@ -513,30 +527,32 @@
|
|||
var ch = input.charCodeAt(tokPos);
|
||||
if (ch === 32) { // ' '
|
||||
++tokPos;
|
||||
} else if(ch === 13) {
|
||||
} else if (ch === 13) {
|
||||
++tokPos;
|
||||
var next = input.charCodeAt(tokPos);
|
||||
if(next === 10) {
|
||||
if (next === 10) {
|
||||
++tokPos;
|
||||
}
|
||||
if(options.locations) {
|
||||
if (options.locations) {
|
||||
++tokCurLine;
|
||||
tokLineStart = tokPos;
|
||||
}
|
||||
} else if (ch === 10) {
|
||||
} else if (ch === 10 || ch === 8232 || ch === 8233) {
|
||||
++tokPos;
|
||||
++tokCurLine;
|
||||
tokLineStart = tokPos;
|
||||
} else if(ch < 14 && ch > 8) {
|
||||
if (options.locations) {
|
||||
++tokCurLine;
|
||||
tokLineStart = tokPos;
|
||||
}
|
||||
} else if (ch > 8 && ch < 14) {
|
||||
++tokPos;
|
||||
} else if (ch === 47) { // '/'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 42) { // '*'
|
||||
skipBlockComment();
|
||||
} else if (next === 47) { // '/'
|
||||
skipLineComment();
|
||||
} else break;
|
||||
} else if ((ch < 14 && ch > 8) || ch === 32 || ch === 160) { // ' ', '\xa0'
|
||||
} else if (ch === 160) { // '\xa0'
|
||||
++tokPos;
|
||||
} else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
|
||||
++tokPos;
|
||||
|
@ -559,61 +575,79 @@
|
|||
// `tokRegexpAllowed` trick does not work. See `parseStatement`.
|
||||
|
||||
function readToken_dot() {
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next >= 48 && next <= 57) return readNumber(true);
|
||||
++tokPos;
|
||||
return finishToken(_dot);
|
||||
}
|
||||
|
||||
function readToken_slash() { // '/'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (tokRegexpAllowed) {++tokPos; return readRegexp();}
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_slash, 1);
|
||||
}
|
||||
|
||||
function readToken_mult_modulo() { // '%*'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_bin10, 1);
|
||||
return finishOp(_multiplyModulo, 1);
|
||||
}
|
||||
|
||||
function readToken_pipe_amp(code) { // '|&'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
if (next === code) return finishOp(code === 124 ? _bin1 : _bin2, 2);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(code === 124 ? _bin3 : _bin5, 1);
|
||||
return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);
|
||||
}
|
||||
|
||||
function readToken_caret() { // '^'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_bin4, 1);
|
||||
return finishOp(_bitwiseXOR, 1);
|
||||
}
|
||||
|
||||
function readToken_plus_min(code) { // '+-'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
if (next === code) return finishOp(_incdec, 2);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === code) {
|
||||
if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&
|
||||
newline.test(input.slice(lastEnd, tokPos))) {
|
||||
// A `-->` line comment
|
||||
tokPos += 3;
|
||||
skipLineComment();
|
||||
skipSpace();
|
||||
return readToken();
|
||||
}
|
||||
return finishOp(_incDec, 2);
|
||||
}
|
||||
if (next === 61) return finishOp(_assign, 2);
|
||||
return finishOp(_plusmin, 1);
|
||||
return finishOp(_plusMin, 1);
|
||||
}
|
||||
|
||||
function readToken_lt_gt(code) { // '<>'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
var size = 1;
|
||||
if (next === code) {
|
||||
size = code === 62 && input.charCodeAt(tokPos+2) === 62 ? 3 : 2;
|
||||
size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;
|
||||
if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);
|
||||
return finishOp(_bin8, size);
|
||||
return finishOp(_bitShift, size);
|
||||
}
|
||||
if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&
|
||||
input.charCodeAt(tokPos + 3) == 45) {
|
||||
// `<!--`, an XML-style comment that should be interpreted as a line comment
|
||||
tokPos += 4;
|
||||
skipLineComment();
|
||||
skipSpace();
|
||||
return readToken();
|
||||
}
|
||||
if (next === 61)
|
||||
size = input.charCodeAt(tokPos+2) === 61 ? 3 : 2;
|
||||
return finishOp(_bin7, size);
|
||||
size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;
|
||||
return finishOp(_relational, size);
|
||||
}
|
||||
|
||||
|
||||
function readToken_eq_excl(code) { // '=!'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
if (next === 61) return finishOp(_bin6, input.charCodeAt(tokPos+2) === 61 ? 3 : 2);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);
|
||||
return finishOp(code === 61 ? _eq : _prefix, 1);
|
||||
}
|
||||
|
||||
|
@ -638,7 +672,7 @@
|
|||
|
||||
// '0x' is a hexadecimal number.
|
||||
case 48: // '0'
|
||||
var next = input.charCodeAt(tokPos+1);
|
||||
var next = input.charCodeAt(tokPos + 1);
|
||||
if (next === 120 || next === 88) return readHexNumber();
|
||||
// Anything else beginning with a digit is an integer, octal
|
||||
// number, or float.
|
||||
|
@ -693,7 +727,7 @@
|
|||
// Identifier or keyword. '\uXXXX' sequences are allowed in
|
||||
// identifiers, so '\' also dispatches to that.
|
||||
if (isIdentifierStart(code) || code === 92 /* '\' */) return readWord();
|
||||
|
||||
|
||||
var tok = getTokenFromCode(code);
|
||||
|
||||
if (tok === false) {
|
||||
|
@ -702,7 +736,7 @@
|
|||
var ch = String.fromCharCode(code);
|
||||
if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord();
|
||||
raise(tokPos, "Unexpected character '" + ch + "'");
|
||||
}
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
|
||||
|
@ -735,7 +769,13 @@
|
|||
// here (don't ask).
|
||||
var mods = readWord1();
|
||||
if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag");
|
||||
return finishToken(_regexp, new RegExp(content, mods));
|
||||
try {
|
||||
var value = new RegExp(content, mods);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) raise(start, e.message);
|
||||
raise(e);
|
||||
}
|
||||
return finishToken(_regexp, value);
|
||||
}
|
||||
|
||||
// Read an integer in the given radix. Return null if zero digits
|
||||
|
@ -768,7 +808,7 @@
|
|||
}
|
||||
|
||||
// Read an integer, octal integer, or floating-point number.
|
||||
|
||||
|
||||
function readNumber(startsWithDot) {
|
||||
var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;
|
||||
if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number");
|
||||
|
@ -781,7 +821,7 @@
|
|||
if (next === 69 || next === 101) { // 'eE'
|
||||
next = input.charCodeAt(++tokPos);
|
||||
if (next === 43 || next === 45) ++tokPos; // '+-'
|
||||
if (readInt(10) === null) raise(start, "Invalid number")
|
||||
if (readInt(10) === null) raise(start, "Invalid number");
|
||||
isFloat = true;
|
||||
}
|
||||
if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number");
|
||||
|
@ -810,7 +850,7 @@
|
|||
ch = input.charCodeAt(++tokPos);
|
||||
var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));
|
||||
if (octal) octal = octal[0];
|
||||
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, octal.length - 1);
|
||||
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);
|
||||
if (octal === "0") octal = null;
|
||||
++tokPos;
|
||||
if (octal) {
|
||||
|
@ -837,7 +877,7 @@
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8329) raise(tokStart, "Unterminated string constant");
|
||||
if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant");
|
||||
out += String.fromCharCode(ch); // '\'
|
||||
++tokPos;
|
||||
}
|
||||
|
@ -931,7 +971,7 @@
|
|||
// ### Parser utilities
|
||||
|
||||
// Continue to the next token.
|
||||
|
||||
|
||||
function next() {
|
||||
lastStart = tokStart;
|
||||
lastEnd = tokEnd;
|
||||
|
@ -944,10 +984,12 @@
|
|||
|
||||
function setStrict(strct) {
|
||||
strict = strct;
|
||||
tokPos = lastEnd;
|
||||
while (tokPos < tokLineStart) {
|
||||
tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
|
||||
--tokCurLine;
|
||||
tokPos = tokStart;
|
||||
if (options.locations) {
|
||||
while (tokPos < tokLineStart) {
|
||||
tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1;
|
||||
--tokCurLine;
|
||||
}
|
||||
}
|
||||
skipSpace();
|
||||
readToken();
|
||||
|
@ -971,6 +1013,8 @@
|
|||
var node = new node_t();
|
||||
if (options.locations)
|
||||
node.loc = new node_loc_t();
|
||||
if (options.directSourceFile)
|
||||
node.sourceFile = options.directSourceFile;
|
||||
if (options.ranges)
|
||||
node.range = [tokStart, 0];
|
||||
return node;
|
||||
|
@ -1095,7 +1139,7 @@
|
|||
// does not help.
|
||||
|
||||
function parseStatement() {
|
||||
if (tokType === _slash)
|
||||
if (tokType === _slash || tokType === _assign && tokVal == "/=")
|
||||
readToken(true);
|
||||
|
||||
var starttype = tokType, node = startNode();
|
||||
|
@ -1159,6 +1203,7 @@
|
|||
var init = startNode();
|
||||
next();
|
||||
parseVar(init, true);
|
||||
finishNode(init, "VariableDeclaration");
|
||||
if (init.declarations.length === 1 && eat(_in))
|
||||
return parseForIn(node, init);
|
||||
return parseFor(node, init);
|
||||
|
@ -1185,7 +1230,7 @@
|
|||
// In `return` (and `break`/`continue`), the keywords with
|
||||
// optional arguments, we eagerly look for a semicolon or the
|
||||
// possibility to insert one.
|
||||
|
||||
|
||||
if (eat(_semi) || canInsertSemicolon()) node.argument = null;
|
||||
else { node.argument = parseExpression(); semicolon(); }
|
||||
return finishNode(node, "ReturnStatement");
|
||||
|
@ -1200,7 +1245,7 @@
|
|||
// Statements under must be grouped (by label) in SwitchCase
|
||||
// nodes. `cur` is used to keep the node that we are currently
|
||||
// adding statements to.
|
||||
|
||||
|
||||
for (var cur, sawDefault; tokType != _braceR;) {
|
||||
if (tokType === _case || tokType === _default) {
|
||||
var isCase = tokType === _case;
|
||||
|
@ -1248,6 +1293,7 @@
|
|||
clause.body = parseBlock();
|
||||
node.handler = finishNode(clause, "CatchClause");
|
||||
}
|
||||
node.guardedHandlers = empty;
|
||||
node.finalizer = eat(_finally) ? parseBlock() : null;
|
||||
if (!node.handler && !node.finalizer)
|
||||
raise(node.start, "Missing catch or finally clause");
|
||||
|
@ -1255,9 +1301,9 @@
|
|||
|
||||
case _var:
|
||||
next();
|
||||
node = parseVar(node);
|
||||
parseVar(node);
|
||||
semicolon();
|
||||
return node;
|
||||
return finishNode(node, "VariableDeclaration");
|
||||
|
||||
case _while:
|
||||
next();
|
||||
|
@ -1327,11 +1373,11 @@
|
|||
while (!eat(_braceR)) {
|
||||
var stmt = parseStatement();
|
||||
node.body.push(stmt);
|
||||
if (first && isUseStrict(stmt)) {
|
||||
if (first && allowStrict && isUseStrict(stmt)) {
|
||||
oldStrict = strict;
|
||||
setStrict(strict = true);
|
||||
}
|
||||
first = false
|
||||
first = false;
|
||||
}
|
||||
if (strict && !oldStrict) setStrict(false);
|
||||
return finishNode(node, "BlockStatement");
|
||||
|
@ -1378,7 +1424,7 @@
|
|||
node.declarations.push(finishNode(decl, "VariableDeclarator"));
|
||||
if (!eat(_comma)) break;
|
||||
}
|
||||
return finishNode(node, "VariableDeclaration");
|
||||
return node;
|
||||
}
|
||||
|
||||
// ### Expression parsing
|
||||
|
@ -1439,7 +1485,7 @@
|
|||
// Start the precedence parser.
|
||||
|
||||
function parseExprOps(noIn) {
|
||||
return parseExprOp(parseMaybeUnary(noIn), -1, noIn);
|
||||
return parseExprOp(parseMaybeUnary(), -1, noIn);
|
||||
}
|
||||
|
||||
// Parse binary operators with the operator precedence parsing
|
||||
|
@ -1455,10 +1501,11 @@
|
|||
var node = startNodeFrom(left);
|
||||
node.left = left;
|
||||
node.operator = tokVal;
|
||||
var op = tokType;
|
||||
next();
|
||||
node.right = parseExprOp(parseMaybeUnary(noIn), prec, noIn);
|
||||
var node = finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
|
||||
return parseExprOp(node, minPrec, noIn);
|
||||
node.right = parseExprOp(parseMaybeUnary(), prec, noIn);
|
||||
var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression");
|
||||
return parseExprOp(exprNode, minPrec, noIn);
|
||||
}
|
||||
}
|
||||
return left;
|
||||
|
@ -1466,13 +1513,14 @@
|
|||
|
||||
// Parse unary operators, both prefix and postfix.
|
||||
|
||||
function parseMaybeUnary(noIn) {
|
||||
function parseMaybeUnary() {
|
||||
if (tokType.prefix) {
|
||||
var node = startNode(), update = tokType.isUpdate;
|
||||
node.operator = tokVal;
|
||||
node.prefix = true;
|
||||
tokRegexpAllowed = true;
|
||||
next();
|
||||
node.argument = parseMaybeUnary(noIn);
|
||||
node.argument = parseMaybeUnary();
|
||||
if (update) checkLVal(node.argument);
|
||||
else if (strict && node.operator === "delete" &&
|
||||
node.argument.type === "Identifier")
|
||||
|
@ -1543,7 +1591,7 @@
|
|||
case _null: case _true: case _false:
|
||||
var node = startNode();
|
||||
node.value = tokType.atomValue;
|
||||
node.raw = tokType.keyword
|
||||
node.raw = tokType.keyword;
|
||||
next();
|
||||
return finishNode(node, "Literal");
|
||||
|
||||
|
@ -1586,14 +1634,14 @@
|
|||
|
||||
// New's precedence is slightly tricky. It must allow its argument
|
||||
// to be a `[]` or dot subscript expression, but not a call — at
|
||||
// least, not without wrapping it in parentheses. Thus, it uses the
|
||||
// least, not without wrapping it in parentheses. Thus, it uses the
|
||||
|
||||
function parseNew() {
|
||||
var node = startNode();
|
||||
next();
|
||||
node.callee = parseSubscripts(parseExprAtom(), true);
|
||||
if (eat(_parenL)) node.arguments = parseExprList(_parenR, false);
|
||||
else node.arguments = [];
|
||||
else node.arguments = empty;
|
||||
return finishNode(node, "NewExpression");
|
||||
}
|
||||
|
||||
|
@ -1712,6 +1760,7 @@
|
|||
function parseIdent(liberal) {
|
||||
var node = startNode();
|
||||
node.name = tokType === _name ? tokVal : (liberal && !options.forbidReserved && tokType.keyword) || unexpected();
|
||||
tokRegexpAllowed = false;
|
||||
next();
|
||||
return finishNode(node, "Identifier");
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче