--- a/accessible/src/html/nsHTMLTableAccessible.cpp
+++ b/accessible/src/html/nsHTMLTableAccessible.cpp
@@ -1409,16 +1409,19 @@ nsHTMLTableAccessible::IsProbablyForLayo
if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) {
// Role attribute is present, but overridden roles have already been dealt with.
// Only landmarks and other roles that don't override the role from native
// markup are left to deal with here.
RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table");
}
+ if (mContent->Tag() != nsGkAtoms::table)
+ RETURN_LAYOUT_ANSWER(true, "table built by CSS display:table style");
+
// Check if datatable attribute has "0" value.
if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable,
NS_LITERAL_STRING("0"), eCaseMatters)) {
RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout");
}
// Check for legitimate data table attributes.
nsAutoString summary;
--- a/accessible/src/msaa/CAccessibleText.cpp
+++ b/accessible/src/msaa/CAccessibleText.cpp
@@ -171,16 +171,18 @@ CAccessibleText::get_characterExtents(lo
STDMETHODIMP
CAccessibleText::get_nSelections(long *aNSelections)
{
__try {
*aNSelections = 0;
nsRefPtr<nsHyperTextAccessible> textAcc(do_QueryObject(this));
+ if (textAcc->IsDefunct())
+ return E_FAIL;
PRInt32 selCount = 0;
nsresult rv = textAcc->GetSelectionCount(&selCount);
if (NS_FAILED(rv))
return GetHRESULT(rv);
*aNSelections = selCount;
return S_OK;
--- a/accessible/tests/mochitest/table/test_layoutguess.html
+++ b/accessible/tests/mochitest/table/test_layoutguess.html
@@ -93,16 +93,19 @@
testAttrs("table21.3", attr, true);
testAttrs("table21.4", attr, true);
testAttrs("table21.5", attr, true);
testAttrs("table21.6", attr, true);
// layout table having datatable="0" attribute and containing data table structure (tfoot element)
testAttrs("table22", attr, true);
+ // css table with non-table tag
+ testAttrs("table23", attr, true);
+
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest);
</script>
</head>
<body>
@@ -112,16 +115,21 @@
title="Don't treat tables that have a landmark role as layout table">
Mozilla Bug 495388
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=690222"
title="Data table elements used to determine layout-guess attribute shouldn't be picked from nested tables">
Mozilla Bug 690222
</a>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=693948"
+ title="Expose layout-guess: true object attribute on CSS table accessible">
+ Mozilla Bug 693948
+ </a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<!-- Table with role of grid -->
<table id="table1" role="grid">
@@ -426,10 +434,19 @@
<!-- layout table with datatable="0" and tfoot element-->
<table id="table22" datatable="0">
<tfoot>
<tr>
<td>Cell1</td><td>cell2</td>
</tr>
</tfoot>
</table>
+
+ <!-- css table with noon-table tag -->
+ <div id="table23" style="display:table;">
+ <div style="display:table-row;">
+ <div style="display:table-cell;">Row 1, column 1</div>
+ <div style="display:table-cell;">Row 1, column 2</div>
+ <div style="display:table-cell;">Row 1, column 3</div>
+ </div>
+ </div>
</body>
</html>
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -49,17 +49,17 @@
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
-<?xml-stylesheet href="chrome://global/skin/webConsole.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/webconsole.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
# All DTD information is stored in a separate file so that it can be shared by
# hiddenWindow.xul.
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -7,20 +7,20 @@
top: 0;
left: 0;
}
#highlighter-veil-container {
overflow: hidden;
}
-#highlighter-veil-container:not([locked]) > .highlighter-veil,
-#highlighter-veil-container:not([locked]) > #highlighter-veil-middlebox,
-#highlighter-veil-container:not([locked]) > #highlighter-veil-middlebox > .highlighter-veil,
-#highlighter-veil-container:not([locked]) > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
+#highlighter-veil-container:not([disable-transitions]) > .highlighter-veil,
+#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox,
+#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > .highlighter-veil,
+#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
-moz-transition-property: width, height;
-moz-transition-duration: 0.1s;
-moz-transition-timing-function: linear;
}
#highlighter-veil-bottombox,
#highlighter-veil-rightbox {
-moz-box-flex: 1;
@@ -46,28 +46,39 @@
/*
* Node Infobar
*/
#highlighter-nodeinfobar-container {
position: absolute;
}
-#highlighter-nodeinfobar-container:not([locked]) {
+#highlighter-nodeinfobar-container:not([disable-transitions]) {
-moz-transition-property: top, left;
-moz-transition-duration: 0.1s;
-moz-transition-timing-function: linear;
}
#highlighter-nodeinfobar {
display: block;
white-space: nowrap;
direction: ltr;
}
+#highlighter-nodeinfobar-container[locked] > #highlighter-nodeinfobar {
+ pointer-events: auto;
+}
+
+#highlighter-nodeinfobar-id,
+.highlighter-nodeinfobar-class,
+#highlighter-nodeinfobar-tagname {
+ -moz-user-select: text;
+ cursor: text;
+}
+
.highlighter-nodeinfobar-arrow {
display: none;
}
#highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > #highlighter-nodeinfobar-arrow-bottom {
display: block;
}
@@ -78,19 +89,11 @@
#highlighter-nodeinfobar-container[disabled] {
visibility: hidden;
}
#highlighter-nodeinfobar-id:empty {
display: none;
}
-#highlighter-nodeinfobar-id::before {
- content: "#";
-}
-
-.highlighter-nodeinfobar-class::before {
- content: ".";
-}
-
#highlighter-nodeinfobar-tagname {
text-transform: lowercase;
}
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -854,25 +854,25 @@ nsContextMenu.prototype = {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
var ctxDraw = canvas.getContext("2d");
ctxDraw.drawImage(video, 0, 0);
saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject);
},
fullScreenVideo: function () {
- let video = this.target;
+ let video = this.target;
if (document.mozFullScreenEnabled)
video.mozRequestFullScreen();
else {
- // Fallback for the legacy full-screen video implementation.
- video.pause();
- openDialog("chrome://browser/content/fullscreen-video.xhtml",
- "", "chrome,centerscreen,dialog=no", video);
- }
+ // Fallback for the legacy full-screen video implementation.
+ video.pause();
+ openDialog("chrome://browser/content/fullscreen-video.xhtml",
+ "", "chrome,centerscreen,dialog=no", video);
+ }
},
// Change current window to the URL of the background image.
viewBGImage: function(e) {
urlSecurityCheck(this.bgImageURL,
this.browser.contentPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
var doc = this.target.ownerDocument;
--- a/browser/build.mk
+++ b/browser/build.mk
@@ -50,16 +50,32 @@ tier_app_dirs += $(MOZ_BRANDING_DIRECTOR
ifdef MOZ_SERVICES_SYNC
tier_app_dirs += services
endif
tier_app_dirs += browser
# Never add other tier_app_dirs after browser. They won't get packaged
# properly on mac.
+################################################
+# Parallel build on Windows with GNU make check
+
+default::
+ifeq (,$(findstring pymake,$(MAKE)))
+ifeq ($(HOST_OS_ARCH),WINNT)
+ifneq (1,$(NUMBER_OF_PROCESSORS))
+ @echo $(if $(findstring -j,$(value MAKEFLAGS)), \
+$(error You are using GNU make to build Firefox with -jN on Windows. \
+This will randomly deadlock. To compile a parallel build on Windows \
+run "python -OO build/pymake/make.py -f client.mk build". \
+See https://developer.mozilla.org/en/pymake for more details.))
+endif
+endif
+endif
+
installer:
@$(MAKE) -C browser/installer installer
package:
@$(MAKE) -C browser/installer
package-compare:
@$(MAKE) -C browser/installer package-compare
--- a/browser/components/migration/src/ChromeProfileMigrator.js
+++ b/browser/components/migration/src/ChromeProfileMigrator.js
@@ -474,33 +474,33 @@ ChromeProfileMigrator.prototype = {
return result;
},
/*
* Whether we support migration of Chrome
*
* @return true if supported
*/
- sourceExists: function Chrome_sourceExists()
+ get sourceExists()
{
let result = this.getMigrateData(null, false);
return result != 0;
},
// Although Chrome supports multi-profiles, there is no way
// to get profile lists.
sourceHasMultipleProfiles: false,
sourceProfiles: null,
/*
* Return home page URL
*
* @return home page URL
*/
- sourceHomePageURL: function Chrome_sourceHomePageURL()
+ get sourceHomePageURL()
{
try {
if (this._homepageURL)
return this._homepageURL;
if (!this._paths.prefs)
this.getMigrateData(null, false);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1527,39 +1527,39 @@ ContentPermissionPrompt.prototype = {
},
};
var message;
var secondaryActions = [];
// Different message/options if it is a local file
if (requestingURI.schemeIs("file")) {
- message = browserBundle.formatStringFromName("geolocation.fileWantsToKnow",
+ message = browserBundle.formatStringFromName("geolocation.shareWithFile",
[requestingURI.path], 1);
} else {
- message = browserBundle.formatStringFromName("geolocation.siteWantsToKnow",
+ message = browserBundle.formatStringFromName("geolocation.shareWithSite",
[requestingURI.host], 1);
// Don't offer to "always/never share" in PB mode
var inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
getService(Ci.nsIPrivateBrowsingService).
privateBrowsingEnabled;
if (!inPrivateBrowsing) {
secondaryActions.push({
- label: browserBundle.GetStringFromName("geolocation.alwaysShare"),
- accessKey: browserBundle.GetStringFromName("geolocation.alwaysShare.accesskey"),
+ label: browserBundle.GetStringFromName("geolocation.alwaysShareLocation"),
+ accessKey: browserBundle.GetStringFromName("geolocation.alwaysShareLocation.accesskey"),
callback: function () {
Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.ALLOW_ACTION);
request.allow();
}
});
secondaryActions.push({
- label: browserBundle.GetStringFromName("geolocation.neverShare"),
- accessKey: browserBundle.GetStringFromName("geolocation.neverShare.accesskey"),
+ label: browserBundle.GetStringFromName("geolocation.neverShareLocation"),
+ accessKey: browserBundle.GetStringFromName("geolocation.neverShareLocation.accesskey"),
callback: function () {
Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.DENY_ACTION);
request.cancel();
}
});
}
}
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -354,17 +354,17 @@ SessionStoreService.prototype = {
if (lastSessionCrashed) {
this._recentCrashes = (this._initialState.session &&
this._initialState.session.recentCrashes || 0) + 1;
if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
// replace the crashed session with a restore-page-only session
let pageData = {
url: "about:sessionrestore",
- formdata: { "#sessionData": JSON.stringify(this._initialState) }
+ formdata: { "#sessionData": this._initialState }
};
this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
}
}
// Load the session start time from the previous state
this._sessionStartTime = this._initialState.session &&
this._initialState.session.startTime ||
@@ -2135,20 +2135,27 @@ SessionStoreService.prototype = {
for (var i = 0; i < aContent.frames.length; i++) {
if (aData.children && aData.children[i])
this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
aData.children[i], aUpdateFormData,
aFullData, aIsPinned);
}
var isHTTPS = this._getURIFromString((aContent.parent || aContent).
document.location.href).schemeIs("https");
- if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) ||
- aContent.top.document.location.href == "about:sessionrestore") {
+ let isAboutSR = aContent.top.document.location.href == "about:sessionrestore";
+ if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
if (aFullData || aUpdateFormData) {
let formData = this._collectFormDataForFrame(aContent.document);
+
+ // We want to avoid saving data for about:sessionrestore as a string.
+ // Since it's stored in the form as stringified JSON, stringifying further
+ // causes an explosion of escape characters. cf. bug 467409
+ if (formData && isAboutSR)
+ formData["#sessionData"] = JSON.parse(formData["#sessionData"]);
+
if (formData)
aData.formdata = formData;
else if (aData.formdata)
delete aData.formdata;
}
// designMode is undefined e.g. for XUL documents (as about:config)
if ((aContent.document.designMode || "") == "on") {
@@ -2976,18 +2983,19 @@ SessionStoreService.prototype = {
* @param aTabData
* Array of tab data
* @param aIdMap
* Hash for ensuring unique frame IDs
*/
restoreHistory:
function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap) {
var _this = this;
- while (aTabs.length > 0 && (!aTabs[0].linkedBrowser.__SS_tabStillLoading || !aTabs[0].parentNode)) {
- aTabs.shift(); // this tab got removed before being completely restored
+ // if the tab got removed before being completely restored, then skip it
+ while (aTabs.length > 0 && !(this._canRestoreTabHistory(aTabs[0]))) {
+ aTabs.shift();
aTabData.shift();
}
if (aTabs.length == 0) {
// At this point we're essentially ready for consumers to read/write data
// via the sessionstore API so we'll send the SSWindowStateReady event.
this._setWindowStateReady(aWindow);
return; // no more tabs to restore
}
@@ -3374,16 +3382,23 @@ SessionStoreService.prototype = {
let node = key.charAt(0) == "#" ? aDocument.getElementById(key.slice(1)) :
XPathHelper.resolve(aDocument, key);
if (!node)
continue;
let eventType;
let value = aData[key];
+
+ // for about:sessionrestore we saved the field as JSON to avoid nested
+ // instances causing humongous sessionstore.js files. cf. bug 467409
+ if (aURL == "about:sessionrestore" && typeof value == "object") {
+ value = JSON.stringify(value);
+ }
+
if (typeof value == "string" && node.type != "file") {
if (node.value == value)
continue; // don't dispatch an input event for no change
node.value = value;
eventType = "input";
}
else if (typeof value == "boolean") {
@@ -3992,16 +4007,30 @@ SessionStoreService.prototype = {
// store this tab's data.
return aTabState.entries.length &&
!(aTabState.entries.length == 1 &&
aTabState.entries[0].url == "about:blank" &&
!aTabState.userTypedValue);
},
/**
+ * Determine if we can restore history into this tab.
+ * This will be false when a tab has been removed (usually between
+ * restoreHistoryPrecursor && restoreHistory) or if the tab is still marked
+ * as loading.
+ *
+ * @param aTab
+ * @returns boolean
+ */
+ _canRestoreTabHistory: function sss__canRestoreTabHistory(aTab) {
+ return aTab.parentNode && aTab.linkedBrowser &&
+ aTab.linkedBrowser.__SS_tabStillLoading;
+ },
+
+ /**
* This is going to take a state as provided at startup (via
* nsISessionStartup.state) and split it into 2 parts. The first part
* (defaultState) will be a state that should still be restored at startup,
* while the second part (state) is a state that should be saved for later.
* defaultState will be comprised of windows with only pinned tabs, extracted
* from state. It will contain the cookies that go along with the history
* entries in those tabs. It will also contain window position information.
*
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -95,16 +95,17 @@ include $(topsrcdir)/config/rules.mk
browser_464620_a.html \
browser_464620_b.js \
browser_464620_b.html \
browser_464620_xd.html \
browser_465215.js \
browser_465223.js \
browser_466937.js \
browser_466937_sample.html \
+ browser_467409-backslashplosion.js \
browser_477657.js \
browser_480148.js \
browser_480893.js \
browser_483330.js \
browser_485482.js \
browser_485482_sample.html \
browser_485563.js \
browser_490040.js \
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test Summary:
+// 1. Open about:sessionrestore via setBrowserState where formdata is a JS object, not a string
+// 1a. Check that #sessionData on the page is readable after JSON.parse (skipped, checking formdata is sufficient)
+// 1b. Check that there are no backslashes in the formdata
+// 1c. Check that formdata (via getBrowserState) doesn't require JSON.parse
+//
+// 2. Use the current state (currently about:sessionrestore with data) and then open than in a new instance of about:sessionrestore
+// 2a. Check that there are no backslashes in the formdata
+// 2b. Check that formdata (via getBrowserState) doesn't require JSON.parse
+//
+// 3. [backwards compat] Use a stringified state as formdata when opening about:sessionrestore
+// 3a. Make sure there are nodes in the tree on about:sessionrestore (skipped, checking formdata is sufficient)
+// 3b. Check that there are no backslashes in the formdata
+// 3c. Check that formdata (via getBrowserState) doesn't require JSON.parse
+
+function test() {
+ waitForExplicitFinish();
+
+ let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
+ let crashState = { windows: [{ tabs: [{ entries: [{ url: "about:mozilla" }] }]}]};
+
+ let pagedata = { url: "about:sessionrestore",
+ formdata: { "#sessionData": crashState } };
+ let state = { windows: [{ tabs: [{ entries: [pagedata] }] }] };
+
+ // test1 calls test2 calls test3 calls finish
+ test1(state);
+
+
+ function test1(aState) {
+ waitForBrowserState(aState, function() {
+ checkState("test1", test2);
+ });
+ }
+
+ function test2(aState) {
+ let pagedata2 = { url: "about:sessionrestore",
+ formdata: { "#sessionData": aState } };
+ let state2 = { windows: [{ tabs: [{ entries: [pagedata2] }] }] };
+
+ waitForBrowserState(state2, function() {
+ checkState("test2", test3);
+ });
+ }
+
+ function test3(aState) {
+ let pagedata3 = { url: "about:sessionrestore",
+ formdata: { "#sessionData": JSON.stringify(crashState) } };
+ let state3 = { windows: [{ tabs: [{ entries: [pagedata3] }] }] };
+ waitForBrowserState(state3, function() {
+ // In theory we should do inspection of the treeview on about:sessionrestore,
+ // but we don't actually need to. If we fail tests in checkState then
+ // about:sessionrestore won't be able to turn the form value into a usable page.
+ checkState("test3", function() waitForBrowserState(blankState, finish));
+ });
+ }
+
+ function checkState(testName, callback) {
+ let curState = JSON.parse(ss.getBrowserState());
+ let formdata = curState.windows[0].tabs[0].entries[0].formdata;
+
+ ok(formdata["#sessionData"], testName + ": we have form data for about:sessionrestore");
+
+ let sessionData_raw = JSON.stringify(formdata["#sessionData"]);
+ ok(!/\\/.test(sessionData_raw), testName + ": #sessionData contains no backslashes");
+ info(sessionData_raw);
+
+ let gotError = false;
+ try {
+ JSON.parse(formdata["#sessionData"]);
+ }
+ catch (e) {
+ info(testName + ": got error: " + e);
+ gotError = true;
+ }
+ ok(gotError, testName + ": attempting to JSON.parse form data threw error");
+
+ // Panorama sticks JSON into extData, which we stringify causing the
+ // naive backslash check to fail. extData doesn't matter in the grand
+ // scheme here, so we'll delete the extData so doesn't end up in future states.
+ delete curState.windows[0].extData;
+ delete curState.windows[0].tabs[0].extData;
+ callback(curState);
+ }
+
+}
+
--- a/browser/components/sessionstore/test/browser/browser_590563.js
+++ b/browser/components/sessionstore/test/browser/browser_590563.js
@@ -7,17 +7,17 @@ function test() {
tabs: [
{ entries: [{ url: "about:robots" }], hidden: true },
{ entries: [{ url: "about:blank" }], hidden: false }
]
}]
};
let pageData = {
url: "about:sessionrestore",
- formdata: { "#sessionData": "(" + JSON.stringify(oldState) + ")" }
+ formdata: { "#sessionData": oldState }
};
let state = { windows: [{ tabs: [{ entries: [pageData] }] }] };
waitForExplicitFinish();
newWindowWithState(state, function (win) {
registerCleanupFunction(function () win.close());
--- a/browser/components/tabview/groupitems.js
+++ b/browser/components/tabview/groupitems.js
@@ -1086,31 +1086,31 @@ GroupItem.prototype = Utils.extend(new I
// options - An optional object with settings for this call. See below.
//
// Possible options:
// dontArrange - don't rearrange the remaining items
// dontClose - don't close the group even if it normally would
// immediately - don't animate
remove: function GroupItem_remove(a, options) {
try {
- var $el;
- var item;
+ let $el;
+ let item;
if (a.isAnItem) {
item = a;
$el = iQ(item.container);
} else {
$el = iQ(a);
item = Items.item($el);
}
if (!options)
options = {};
- var index = this._children.indexOf(item);
+ let index = this._children.indexOf(item);
if (index != -1)
this._children.splice(index, 1);
if (item == this._activeTab || !this._activeTab) {
if (this._children.length > 0)
this._activeTab = this._children[0];
else
this._activeTab = null;
@@ -1137,17 +1137,19 @@ GroupItem.prototype = Utils.extend(new I
item.setResizable(true, options.immediately);
// if a blank tab is selected while restoring a tab the blank tab gets
// removed. we need to keep the group alive for the restored tab.
if (item.isRemovedAfterRestore)
options.dontClose = true;
let closed = options.dontClose ? false : this.closeIfEmpty();
- if (closed) {
+ if (closed ||
+ (this._children.length == 0 && !gBrowser.selectedTab.pinned &&
+ !item.isDragging)) {
this._makeLastActiveGroupItemActive();
} else if (!options.dontArrange) {
this.arrange({animate: !options.immediately});
this._unfreezeItemSize({dontArrange: true});
}
this._sendToSubscribers("childRemoved",{ groupItemId: this.id, item: item });
} catch(e) {
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -255,19 +255,25 @@ TabItem.prototype = Utils.extend(new Ite
// Loads the tabItems thumbnail.
loadThumbnail: function TabItem_loadThumbnail(tabData) {
Utils.assert(tabData, "invalid or missing argument <tabData>");
let self = this;
function TabItem_loadThumbnail_callback(error, imageData) {
// we could have been unlinked while waiting for the thumbnail to load
- if (error || !imageData || !self.tab)
+ if (!self.tab)
return;
+ if (error || !imageData) {
+ // paint the canvas to avoid leaving traces when dragging tab over it
+ self.tabCanvas.paint();
+ return;
+ }
+
self._sendToSubscribers("loadedCachedImageData");
// If we have a cached image, then show it if the loaded URL matches
// what the cache is from, OR the loaded URL is blank, which means
// that the page hasn't loaded yet.
let currentUrl = self.tab.linkedBrowser.currentURI.spec;
if (tabData.url == currentUrl || currentUrl == "about:blank")
self.showCachedData(tabData, imageData);
--- a/browser/components/tabview/test/Makefile.in
+++ b/browser/components/tabview/test/Makefile.in
@@ -159,16 +159,17 @@ include $(topsrcdir)/config/rules.mk
browser_tabview_bug673729.js \
browser_tabview_bug677310.js \
browser_tabview_bug679853.js \
browser_tabview_bug681599.js \
browser_tabview_bug685476.js \
browser_tabview_bug685692.js \
browser_tabview_bug686654.js \
browser_tabview_bug697390.js \
+ browser_tabview_bug705621.js \
browser_tabview_click_group.js \
browser_tabview_dragdrop.js \
browser_tabview_exit_button.js \
browser_tabview_expander.js \
browser_tabview_firstrun_pref.js \
browser_tabview_group.js \
browser_tabview_launch.js \
browser_tabview_multiwindow_search.js \
--- a/browser/components/tabview/test/browser_tabview_apptabs.js
+++ b/browser/components/tabview/test/browser_tabview_apptabs.js
@@ -44,16 +44,20 @@ function onTabViewWindowLoaded() {
// create a second group and make sure it gets the icon too
box.offset(box.width + 20, 0);
let groupItemTwo = new contentWindow.GroupItem([],
{ bounds: box, title: "test2" });
is(contentWindow.GroupItems.groupItems.length, 3, "we now have three groups");
is(appTabCount(groupItemTwo), 1,
"there's an app tab icon in the second group");
+ // When the tab was pinned, the last active group with an item got the focus.
+ // Therefore, switching the focus back to group item one.
+ contentWindow.UI.setActive(groupItemOne);
+
// unpin the tab, make sure the icon goes away and the TabItem comes on
gBrowser.unpinTab(xulTab);
is(groupItemOne._children.length, 1, "the app tab's TabItem is back");
is(appTabCount(groupItemOne), 0, "the icon is gone from group one");
is(appTabCount(groupItemTwo), 0, "the icon is gone from group 2");
// pin the tab again
gBrowser.pinTab(xulTab);
--- a/browser/components/tabview/test/browser_tabview_bug595965.js
+++ b/browser/components/tabview/test/browser_tabview_bug595965.js
@@ -99,16 +99,20 @@ function onTabViewShown(win) {
is(tray.css("-moz-column-count"), 1,
"$appTabTray column count is 1");
is(appTabCount(groupItem), 1, "there's now one app tab icon");
ok(!trayContainer.hasClass("appTabTrayContainerTruncated"),
"$appTabTray container does not have .appTabTrayContainerTruncated");
+ // When the tab was pinned, the last active group with an item got the focus.
+ // Therefore, switching the focus back to group item one.
+ contentWindow.UI.setActive(groupItem);
+
// unpin the last remaining tab
gBrowser.unpinTab(xulTabs[0]);
is(parseInt(trayContainer.css("width")), 0,
"$appTabTray container is not visible");
is(appTabCount(groupItem), 0, "there are no app tab icons");
new file mode 100644
--- /dev/null
+++ b/browser/components/tabview/test/browser_tabview_bug705621.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ newWindowWithTabView(function(win) {
+ registerCleanupFunction(function() {
+ win.close();
+ });
+
+ let cw = win.TabView.getContentWindow();
+
+ let groupItemOne = cw.GroupItems.groupItems[0];
+ is(groupItemOne.getChildren().length, 1, "Group one has 1 tab item");
+
+ let groupItemTwo = createGroupItemWithBlankTabs(win, 300, 300, 40, 1);
+ is(groupItemTwo.getChildren().length, 1, "Group two has 2 tab items");
+
+ whenTabViewIsHidden(function() {
+ executeSoon(function() {
+ win.gBrowser.removeTab(win.gBrowser.selectedTab);
+ is(cw.UI.getActiveTab(), groupItemOne.getChild(0), "TabItem in Group one is selected");
+ finish();
+ });
+ }, win);
+ groupItemTwo.getChild(0).zoomIn();
+ });
+}
+
--- a/browser/components/tabview/ui.js
+++ b/browser/components/tabview/ui.js
@@ -779,17 +779,17 @@ let UI = {
// if not closing the last tab
if (gBrowser.tabs.length > 1) {
// Don't return to TabView if there are any app tabs
for (let a = 0; a < gBrowser._numPinnedTabs; a++) {
if (!gBrowser.tabs[a].closing)
return;
}
- var groupItem = GroupItems.getActiveGroupItem();
+ let groupItem = GroupItems.getActiveGroupItem();
// 1) Only go back to the TabView tab when there you close the last
// tab of a groupItem.
let closingLastOfGroup = (groupItem &&
groupItem._children.length == 1 &&
groupItem._children[0].tab == tab);
// 2) When a blank tab is active while restoring a closed tab the
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -142,19 +142,26 @@ Highlighter.prototype = {
stack.appendChild(this.highlighterContainer);
// The veil will make the whole page darker except
// for the region of the selected box.
this.buildVeil(this.veilContainer);
this.buildInfobar(controlsBox);
+ if (!this.IUI.store.getValue(this.winID, "inspecting")) {
+ this.veilContainer.setAttribute("locked", true);
+ this.nodeInfo.container.setAttribute("locked", true);
+ }
+
this.browser.addEventListener("resize", this, true);
this.browser.addEventListener("scroll", this, true);
+ this.transitionDisabler = null;
+
this.handleResize();
},
/**
* Build the veil:
*
* <vbox id="highlighter-veil-container">
* <box id="highlighter-veil-topbox" class="highlighter-veil"/>
@@ -482,30 +489,30 @@ Highlighter.prototype = {
* Update node information (tagName#id.class)
*/
updateInfobar: function Highlighter_updateInfobar()
{
// Tag name
this.nodeInfo.tagNameLabel.textContent = this.node.tagName;
// ID
- this.nodeInfo.idLabel.textContent = this.node.id;
+ this.nodeInfo.idLabel.textContent = this.node.id ? "#" + this.node.id : "";
// Classes
let classes = this.nodeInfo.classesBox;
while (classes.hasChildNodes()) {
classes.removeChild(classes.firstChild);
}
if (this.node.className) {
let fragment = this.chromeDoc.createDocumentFragment();
for (let i = 0; i < this.node.classList.length; i++) {
let classLabel = this.chromeDoc.createElement("label");
classLabel.className = "highlighter-nodeinfobar-class plain";
- classLabel.textContent = this.node.classList[i];
+ classLabel.textContent = "." + this.node.classList[i];
fragment.appendChild(classLabel);
}
classes.appendChild(fragment);
}
},
/**
* Move the Infobar to the right place in the highlighter.
@@ -660,31 +667,53 @@ Highlighter.prototype = {
switch (aEvent.type) {
case "click":
this.handleClick(aEvent);
break;
case "mousemove":
this.handleMouseMove(aEvent);
break;
case "resize":
+ this.brieflyDisableTransitions();
this.handleResize(aEvent);
break;
case "dblclick":
case "mousedown":
case "mouseup":
aEvent.stopPropagation();
aEvent.preventDefault();
break;
case "scroll":
+ this.brieflyDisableTransitions();
this.highlight();
break;
}
},
/**
+ * Disable the CSS transitions for a short time to avoid laggy animations
+ * during scrolling or resizing.
+ */
+ brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
+ {
+ if (this.transitionDisabler) {
+ this.IUI.win.clearTimeout(this.transitionDisabler);
+ } else {
+ this.veilContainer.setAttribute("disable-transitions", "true");
+ this.nodeInfo.container.setAttribute("disable-transitions", "true");
+ }
+ this.transitionDisabler =
+ this.IUI.win.setTimeout(function() {
+ this.veilContainer.removeAttribute("disable-transitions");
+ this.nodeInfo.container.removeAttribute("disable-transitions");
+ this.transitionDisabler = null;
+ }.bind(this), 500);
+ },
+
+ /**
* Handle clicks.
*
* @param nsIDOMEvent aEvent
* The DOM event.
*/
handleClick: function Highlighter_handleClick(aEvent)
{
// Stop inspection when the user clicks on a node.
@@ -958,16 +987,18 @@ InspectorUI.prototype = {
},
/**
* Initialize highlighter.
*/
initializeHighlighter: function IUI_initializeHighlighter()
{
this.highlighter = new Highlighter(this);
+ this.browser.addEventListener("keypress", this, true);
+ this.highlighter.highlighterContainer.addEventListener("keypress", this, true);
this.highlighterReady();
},
/**
* Initialize the InspectorStore.
*/
initializeStore: function IUI_initializeStore()
{
@@ -1081,20 +1112,16 @@ InspectorUI.prototype = {
startInspecting: function IUI_startInspecting()
{
// if currently editing an attribute value, starting
// "live inspection" mode closes the editor
if (this.treePanel && this.treePanel.editingContext)
this.treePanel.closeEditor();
this.inspectToolbutton.checked = true;
- // Attach event listeners to content window and child windows to enable
- // highlighting and click to stop inspection.
- this.browser.addEventListener("keypress", this, true);
- this.highlighter.highlighterContainer.addEventListener("keypress", this, true);
this.highlighter.attachInspectListeners();
this.inspecting = true;
this.toolsDim(true);
this.highlighter.veilContainer.removeAttribute("locked");
this.highlighter.nodeInfo.container.removeAttribute("locked");
},
--- a/browser/devtools/highlighter/test/browser_inspector_infobar.js
+++ b/browser/devtools/highlighter/test/browser_inspector_infobar.js
@@ -19,19 +19,19 @@ function test()
let style = "body{width:100%;height: 100%} div {position: absolute;height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {height: 100%}";
let html = "<style>" + style + "</style><div id=vertical></div><div id=top class='class1 class2'></div><div id=bottom></div>"
content.location = "data:text/html," + encodeURIComponent(html);
function setupInfobarTest()
{
nodes = [
- {node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "top", classes: "class1 class2"},
- {node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "vertical", classes: ""},
- {node: doc.querySelector("#bottom"), position: "top", tag: "DIV", id: "bottom", classes: ""},
+ {node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "#top", classes: ".class1 .class2"},
+ {node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "#vertical", classes: ""},
+ {node: doc.querySelector("#bottom"), position: "top", tag: "DIV", id: "#bottom", classes: ""},
{node: doc.querySelector("body"), position: "overlap", tag: "BODY", id: "", classes: ""},
]
for (let i = 0; i < nodes.length; i++) {
ok(nodes[i].node, "node " + i + " found");
}
Services.obs.addObserver(runTests,
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -182,20 +182,28 @@ function inspectorFocusTab2()
// Make sure the inspector is still open.
ok(!InspectorUI.inspecting, "Inspector is not highlighting");
ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
is(InspectorUI.store.length, 2, "Inspector.store.length is 2");
isnot(InspectorUI.selection, div, "selection does not match the div element");
- // Switch back to tab 1.
- Services.obs.addObserver(inspectorSecondFocusTab1,
- InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
- gBrowser.selectedTab = tab1;
+ // Make sure keybindings still sork
+ EventUtils.synthesizeKey("VK_RETURN", { });
+
+ executeSoon(function() {
+ ok(InspectorUI.inspecting, "Inspector is highlighting");
+ InspectorUI.toggleInspection();
+
+ // Switch back to tab 1.
+ Services.obs.addObserver(inspectorSecondFocusTab1,
+ InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+ gBrowser.selectedTab = tab1;
+ });
}
function inspectorSecondFocusTab1()
{
Services.obs.removeObserver(inspectorSecondFocusTab1,
InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
ok(InspectorUI.inspecting, "Inspector is highlighting");
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -24,16 +24,17 @@
* David Dahl <ddahl@mozilla.com> (original author)
* Rob Campbell <rcampbell@mozilla.com>
* Johnathan Nightingale <jnightingale@mozilla.com>
* Patrick Walton <pcwalton@mozilla.com>
* Julian Viereck <jviereck@mozilla.com>
* Mihai Șucan <mihai.sucan@gmail.com>
* Michael Ratcliffe <mratcliffe@mozilla.com>
* Joe Walker <jwalker@mozilla.com>
+ * Sonny Piers <sonny.piers@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -279,16 +280,19 @@ const ERRORS = { LOG_MESSAGE_MISSING_ARG
CANNOT_GET_HUD: "Cannot getHeads Up Display with provided ID",
MISSING_ARGS: "Missing arguments",
LOG_OUTPUT_FAILED: "Log Failure: Could not append messageNode to outputNode",
};
// The indent of a console group in pixels.
const GROUP_INDENT = 12;
+// The pref prefix for webconsole filters
+const PREFS_PREFIX = "devtools.webconsole.filter.";
+
/**
* Implements the nsIStreamListener and nsIRequestObserver interface. Used
* within the HS_httpObserverFactory function to get the response body of
* requests.
*
* The code is mostly based on code listings from:
*
* http://www.softwareishard.com/blog/firebug/
@@ -1785,19 +1789,28 @@ HUD_SERVICE.prototype =
* @returns void
*/
registerDisplay: function HS_registerDisplay(aHUDId)
{
// register a display DOM node Id with the service.
if (!aHUDId){
throw new Error(ERRORS.MISSING_ARGS);
}
- this.filterPrefs[aHUDId] = this.defaultFilterPrefs;
- // init storage objects:
- this.storage.createDisplay(aHUDId);
+ this.filterPrefs[aHUDId] = {
+ network: Services.prefs.getBoolPref(PREFS_PREFIX + "network"),
+ networkinfo: Services.prefs.getBoolPref(PREFS_PREFIX + "networkinfo"),
+ csserror: Services.prefs.getBoolPref(PREFS_PREFIX + "csserror"),
+ cssparser: Services.prefs.getBoolPref(PREFS_PREFIX + "cssparser"),
+ exception: Services.prefs.getBoolPref(PREFS_PREFIX + "exception"),
+ jswarn: Services.prefs.getBoolPref(PREFS_PREFIX + "jswarn"),
+ error: Services.prefs.getBoolPref(PREFS_PREFIX + "error"),
+ info: Services.prefs.getBoolPref(PREFS_PREFIX + "info"),
+ warn: Services.prefs.getBoolPref(PREFS_PREFIX + "warn"),
+ log: Services.prefs.getBoolPref(PREFS_PREFIX + "log"),
+ };
},
/**
* When a display is being destroyed, unregister it first
*
* @param string aHUDId
* The ID of a HUD.
* @returns void
@@ -1834,19 +1847,16 @@ HUD_SERVICE.prototype =
}
if (hud.jsterm) {
hud.jsterm.autocompletePopup.destroy();
}
delete this.hudReferences[aHUDId];
- // remove the related storage object
- this.storage.removeDisplay(aHUDId);
-
for (let windowID in this.windowIds) {
if (this.windowIds[windowID] == aHUDId) {
delete this.windowIds[windowID];
}
}
this.unregisterActiveContext(aHUDId);
@@ -1877,20 +1887,16 @@ HUD_SERVICE.prototype =
* @returns void
*/
wakeup: function HS_wakeup()
{
if (Object.keys(this.hudReferences).length > 0) {
return;
}
- this.storage = new ConsoleStorage();
- this.defaultFilterPrefs = this.storage.defaultDisplayPrefs;
- this.defaultGlobalConsolePrefs = this.storage.defaultGlobalConsolePrefs;
-
// begin observing HTTP traffic
this.startHTTPObservation();
HUDWindowObserver.init();
HUDConsoleObserver.init();
ConsoleAPIObserver.init();
},
@@ -1906,20 +1912,17 @@ HUD_SERVICE.prototype =
delete this.httpObserver;
Services.obs.removeObserver(this.httpResponseExaminer,
"http-on-examine-response");
this.openRequests = {};
this.openResponseHeaders = {};
- // delete the storage as it holds onto channels
- delete this.storage;
delete this.defaultFilterPrefs;
- delete this.defaultGlobalConsolePrefs;
delete this.lastFinishedRequestCallback;
HUDWindowObserver.uninit();
HUDConsoleObserver.uninit();
ConsoleAPIObserver.shutdown();
},
@@ -3933,40 +3936,40 @@ HeadsUpDisplay.prototype = {
toolbarButton.setAttribute("tooltip", this.getStr("tip" + name));
toolbarButton.setAttribute("category", aDescriptor.category);
toolbarButton.setAttribute("hudId", this.hudId);
toolbarButton.classList.add("webconsole-filter-button");
let menuPopup = this.makeXULNode("menupopup");
toolbarButton.appendChild(menuPopup);
- let allChecked = true;
+ let someChecked = false;
for (let i = 0; i < aDescriptor.severities.length; i++) {
let severity = aDescriptor.severities[i];
let menuItem = this.makeXULNode("menuitem");
menuItem.setAttribute("label", this.getStr("btn" + severity.name));
menuItem.setAttribute("type", "checkbox");
menuItem.setAttribute("autocheck", "false");
menuItem.setAttribute("hudId", this.hudId);
let prefKey = severity.prefKey;
menuItem.setAttribute("prefKey", prefKey);
let checked = this.filterPrefs[prefKey];
menuItem.setAttribute("checked", checked);
- if (!checked) {
- allChecked = false;
+ if (checked) {
+ someChecked = true;
}
menuItem.addEventListener("command", toggleFilter, false);
menuPopup.appendChild(menuItem);
}
- toolbarButton.setAttribute("checked", allChecked);
+ toolbarButton.setAttribute("checked", someChecked);
},
/**
* Creates the close button on the toolbar.
*
* @param nsIDOMNode aParent
* The toolbar to attach the close button to.
* @return void
@@ -6326,185 +6329,16 @@ HeadsUpDisplayUICommands = {
HUDService.saveRequestAndResponseBodies = checked;
break;
}
}
},
};
-//////////////////////////////////////////////////////////////////////////
-// ConsoleStorage
-//////////////////////////////////////////////////////////////////////////
-
-var prefs = Services.prefs;
-
-const GLOBAL_STORAGE_INDEX_ID = "GLOBAL_CONSOLE";
-const PREFS_PREFIX = "devtools.webconsole.filter.";
-const PREFS = { network: PREFS_PREFIX + "network",
- networkinfo: PREFS_PREFIX + "networkinfo",
- csserror: PREFS_PREFIX + "csserror",
- cssparser: PREFS_PREFIX + "cssparser",
- exception: PREFS_PREFIX + "exception",
- jswarn: PREFS_PREFIX + "jswarn",
- error: PREFS_PREFIX + "error",
- info: PREFS_PREFIX + "info",
- warn: PREFS_PREFIX + "warn",
- log: PREFS_PREFIX + "log",
- };
-
-function ConsoleStorage()
-{
- this.sequencer = null;
- this.consoleDisplays = {};
- // each display will have an index that tracks each ConsoleEntry
- this.displayIndexes = {};
- this.globalStorageIndex = [];
- this.globalDisplay = {};
- this.createDisplay(GLOBAL_STORAGE_INDEX_ID);
- // TODO: need to create a method that truncates the message
- // see bug 570543
-
- this.defaultDisplayPrefs = {
- network: prefs.getBoolPref(PREFS.network),
- networkinfo: prefs.getBoolPref(PREFS.networkinfo),
- csserror: prefs.getBoolPref(PREFS.csserror),
- cssparser: prefs.getBoolPref(PREFS.cssparser),
- exception: prefs.getBoolPref(PREFS.exception),
- jswarn: prefs.getBoolPref(PREFS.jswarn),
- error: prefs.getBoolPref(PREFS.error),
- info: prefs.getBoolPref(PREFS.info),
- warn: prefs.getBoolPref(PREFS.warn),
- log: prefs.getBoolPref(PREFS.log),
- };
-}
-
-ConsoleStorage.prototype = {
-
- sequenceId: function CS_sequencerId()
- {
- if (!this.sequencer) {
- this.sequencer = this.createSequencer();
- }
- return this.sequencer.next();
- },
-
- createSequencer: function CS_createSequencer()
- {
- function sequencer(aInt) {
- while(1) {
- aInt++;
- yield aInt;
- }
- }
- return sequencer(-1);
- },
-
- globalStore: function CS_globalStore(aIndex)
- {
- return this.displayStore(GLOBAL_CONSOLE_DOM_NODE_ID);
- },
-
- displayStore: function CS_displayStore(aId)
- {
- var self = this;
- var idx = -1;
- var id = aId;
- var aLength = self.displayIndexes[id].length;
-
- function displayStoreGenerator(aInt, aLength)
- {
- // create a generator object to iterate through any of the display stores
- // from any index-starting-point
- while(1) {
- // throw if we exceed the length of displayIndexes?
- aInt++;
- var indexIt = self.displayIndexes[id];
- var index = indexIt[aInt];
- if (aLength < aInt) {
- // try to see if we have more entries:
- var newLength = self.displayIndexes[id].length;
- if (newLength > aLength) {
- aLength = newLength;
- }
- else {
- throw new StopIteration();
- }
- }
- var entry = self.consoleDisplays[id][index];
- yield entry;
- }
- }
-
- return displayStoreGenerator(-1, aLength);
- },
-
- recordEntries: function CS_recordEntries(aHUDId, aConfigArray)
- {
- var len = aConfigArray.length;
- for (var i = 0; i < len; i++){
- this.recordEntry(aHUDId, aConfigArray[i]);
- }
- },
-
-
- recordEntry: function CS_recordEntry(aHUDId, aConfig)
- {
- var id = this.sequenceId();
-
- this.globalStorageIndex[id] = { hudId: aHUDId };
-
- var displayStorage = this.consoleDisplays[aHUDId];
-
- var displayIndex = this.displayIndexes[aHUDId];
-
- if (displayStorage && displayIndex) {
- var entry = new ConsoleEntry(aConfig, id);
- displayIndex.push(entry.id);
- displayStorage[entry.id] = entry;
- return entry;
- }
- else {
- throw new Error("Cannot get displayStorage or index object for id " + aHUDId);
- }
- },
-
- getEntry: function CS_getEntry(aId)
- {
- var display = this.globalStorageIndex[aId];
- var storName = display.hudId;
- return this.consoleDisplays[storName][aId];
- },
-
- updateEntry: function CS_updateEntry(aUUID)
- {
- // update an individual entry
- // TODO: see bug 568634
- },
-
- createDisplay: function CS_createdisplay(aId)
- {
- if (!this.consoleDisplays[aId]) {
- this.consoleDisplays[aId] = {};
- this.displayIndexes[aId] = [];
- }
- },
-
- removeDisplay: function CS_removeDisplay(aId)
- {
- try {
- delete this.consoleDisplays[aId];
- delete this.displayIndexes[aId];
- }
- catch (ex) {
- Cu.reportError("Could not remove console display for id " + aId);
- }
- }
-};
-
/**
* A Console log entry
*
* @param JSObject aConfig, object literal with ConsolEntry properties
* @param integer aId
* @returns void
*/
--- a/browser/devtools/webconsole/NetworkPanel.xhtml
+++ b/browser/devtools/webconsole/NetworkPanel.xhtml
@@ -44,17 +44,17 @@
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <link rel="stylesheet" href="chrome://global/skin/webConsole_networkPanel.css" type="text/css"/>
+ <link rel="stylesheet" href="chrome://browser/skin/devtools/webconsole_networkpanel.css" type="text/css"/>
</head>
<body role="application">
<table id="header">
<tr>
<th class="property-name"
scope="row">&networkPanel.requestURL;:</th>
<td class="property-value"
id="headUrl"></td>
--- a/browser/devtools/webconsole/test/browser/Makefile.in
+++ b/browser/devtools/webconsole/test/browser/Makefile.in
@@ -17,16 +17,17 @@
# Portions created by the Initial Developer are Copyright (C) 2010
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# David Dahl <ddahl@mozilla.com>
# Patrick Walton <pcwalton@mozilla.com>
# Mihai Șucan <mihai.sucan@gmail.com>
# Rob Campbell <rcampbell@mozilla.com>
+# Sonny Piers <sonny.piers@gmail.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either of the GNU General Public License Version 2 or later (the "GPL"),
# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
@@ -76,20 +77,16 @@ include $(topsrcdir)/config/rules.mk
browser_webconsole_log_node_classes.js \
browser_webconsole_network_panel.js \
browser_webconsole_jsterm.js \
browser_webconsole_null_and_undefined_output.js \
browser_webconsole_output_order.js \
browser_webconsole_property_panel.js \
browser_webconsole_property_provider.js \
browser_webconsole_registries.js \
- browser_webconsole_storage_create_display.js \
- browser_webconsole_storage_iteration.js \
- browser_webconsole_storage_record_entry.js \
- browser_webconsole_storage_record_many_entries.js \
browser_webconsole_bug_587617_output_copy.js \
browser_webconsole_bug_585237_line_limit.js \
browser_webconsole_bug_581231_close_button.js \
browser_webconsole_bug_582201_duplicate_errors.js \
browser_webconsole_bug_580454_timestamp_l10n.js \
browser_webconsole_netlogging.js \
browser_webconsole_bug_583816_tab_focus.js \
browser_webconsole_bug_594477_clickable_output.js \
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_622303_persistent_filters.js
+++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_622303_persistent_filters.js
@@ -1,59 +1,116 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let prefService = Services.prefs;
- let prefs = [
- "network",
- "networkinfo",
- "csserror",
- "cssparser",
- "exception",
- "jswarn",
- "error",
- "warn",
- "info",
- "log"
- ];
- //Set all prefs to true
- prefs.forEach(function(pref) {
- prefService.setBoolPref("devtools.webconsole.filter." + pref, true);
- });
+ let prefs = {
+ "net": [
+ "network",
+ "networkinfo"
+ ],
+ "css": [
+ "csserror",
+ "cssparser"
+ ],
+ "js": [
+ "exception",
+ "jswarn"
+ ],
+ "logging": [
+ "error",
+ "warn",
+ "info",
+ "log"
+ ]
+ };
+
+ // Set all prefs to true
+ for (let category in prefs) {
+ prefs[category].forEach(function(pref) {
+ prefService.setBoolPref("devtools.webconsole.filter." + pref, true);
+ });
+ }
addTab("about:blank");
openConsole();
-
+
let hud = HUDService.getHudByWindow(content);
- let hudId = HUDService.getHudIdByWindow(content);
+
+ // Check if the filters menuitems exists and are checked
+ for (let category in prefs) {
+ let button = hud.HUDBox.querySelector(".webconsole-filter-button[category=\""
+ + category + "\"]");
+ ok(isChecked(button), "main button for " + category + " category is checked");
- //Check if the filters menuitems exists and is checked
- prefs.forEach(function(pref) {
- let checked = hud.HUDBox.querySelector("menuitem[prefKey=" + pref + "]").
- getAttribute("checked");
- is(checked, "true", "menuitem for " + pref + " exists and is checked");
- });
-
- //Set all prefs to false
- prefs.forEach(function(pref) {
- HUDService.setFilterState(hudId, pref, false);
- });
+ prefs[category].forEach(function(pref) {
+ let menuitem = hud.HUDBox.querySelector("menuitem[prefKey=" + pref + "]");
+ ok(isChecked(menuitem), "menuitem for " + pref + " is checked");
+ });
+ }
+
+ // Set all prefs to false
+ for (let category in prefs) {
+ prefs[category].forEach(function(pref) {
+ HUDService.setFilterState(hud.hudId, pref, false);
+ });
+ }
//Re-init the console
closeConsole();
openConsole();
hud = HUDService.getHudByWindow(content);
-
- //Check if filters menuitems exists and are unchecked
- prefs.forEach(function(pref) {
- let checked = hud.HUDBox.querySelector("menuitem[prefKey=" + pref + "]").
- getAttribute("checked");
- is(checked, "false", "menuitem for " + pref + " exists and is not checked");
- prefService.clearUserPref("devtools.webconsole.filter." + pref);
- });
-
+
+ // Check if the filter button and menuitems are unchecked
+ for (let category in prefs) {
+ let button = hud.HUDBox.querySelector(".webconsole-filter-button[category=\""
+ + category + "\"]");
+ ok(isUnchecked(button), "main button for " + category + " category is not checked");
+
+ prefs[category].forEach(function(pref) {
+ let menuitem = hud.HUDBox.querySelector("menuitem[prefKey=" + pref + "]");
+ ok(isUnchecked(menuitem), "menuitem for " + pref + " is not checked");
+ });
+ }
+
+ // Set first pref in each category to true
+ for (let category in prefs) {
+ HUDService.setFilterState(hud.hudId, prefs[category][0], true);
+ }
+
+ // Re-init the console
+ closeConsole();
+ openConsole();
+
+ hud = HUDService.getHudByWindow(content);
+
+ // Check the main category button is checked and first menuitem is checked
+ for (let category in prefs) {
+ let button = hud.HUDBox.querySelector(".webconsole-filter-button[category=\""
+ + category + "\"]");
+ ok(isChecked(button), category + " button is checked when first pref is true");
+
+ let pref = prefs[category][0];
+ let menuitem = hud.HUDBox.querySelector("menuitem[prefKey=" + pref + "]");
+ ok(isChecked(menuitem), "first " + category + " menuitem is checked");
+ }
+
+ // Clear prefs
+ for (let category in prefs) {
+ prefs[category].forEach(function(pref) {
+ prefService.clearUserPref("devtools.webconsole.filter." + pref);
+ });
+ }
+
gBrowser.removeCurrentTab();
-
finish();
}
+
+function isChecked(aNode) {
+ return aNode.getAttribute("checked") === "true";
+}
+
+function isUnchecked(aNode) {
+ return aNode.getAttribute("checked") === "false";
+}
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_storage_create_display.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is DevTools test code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * David Dahl <ddahl@mozilla.com>
- * Patrick Walton <pcwalton@mozilla.com>
- * Julian Viereck <jviereck@mozilla.com>
- * Mihai Sucan <mihai.sucan@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-// Tests that the console message store is initialized properly.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-console.html";
-
-function test() {
- addTab(TEST_URI);
- browser.addEventListener("DOMContentLoaded", testStorageCreateDisplay,
- false);
-}
-
-function testStorageCreateDisplay() {
- browser.removeEventListener("DOMContentLoaded", testStorageCreateDisplay,
- false);
-
- openConsole();
-
- let cs = HUDService.storage;
-
- ok(typeof cs.consoleDisplays == "object",
- "consoledisplays exist");
- ok(typeof cs.displayIndexes == "object",
- "console indexes exist");
- cs.createDisplay("foo");
- ok(typeof cs.consoleDisplays["foo"] == "object",
- "foo display exists");
- ok(typeof cs.displayIndexes["foo"] == "object",
- "foo index exists");
-
- cs.removeDisplay("foo");
-
- finishTest();
-}
-
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_storage_iteration.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is DevTools test code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * David Dahl <ddahl@mozilla.com>
- * Patrick Walton <pcwalton@mozilla.com>
- * Julian Viereck <jviereck@mozilla.com>
- * Mihai Sucan <mihai.sucan@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-// Test that the iterator API of the console message store works.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-console.html";
-
-function test() {
- addTab(TEST_URI);
- browser.addEventListener("DOMContentLoaded", testStorageIteration, false);
-}
-
-function testStorageIteration() {
- browser.removeEventListener("DOMContentLoaded", testStorageIteration,
- false);
-
- openConsole();
-
- let cs = HUDService.storage;
-
- // Must have enough entries present to avoid exhausting the iterators below.
- cs.createDisplay("foo");
- for (let i = 0; i < 300; i++) {
- cs.recordEntry("foo", { logLevel: "network", message: "foo" });
- }
-
- var id = "foo";
- var it = cs.displayStore(id);
- var entry = it.next();
- var entry2 = it.next();
-
- let entries = [];
- for (var i = 0; i < 100; i++) {
- let _entry = it.next();
- entries.push(_entry);
- }
-
- ok(entries.length == 100, "entries length == 100");
-
- let entries2 = [];
-
- for (var i = 0; i < 100; i++){
- let _entry = it.next();
- entries2.push(_entry);
- }
-
- ok(entries[0].id != entries2[0].id,
- "two distinct pages of log entries");
-
- cs.removeDisplay("foo");
- cs = null;
-
- finishTest();
-}
-
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_storage_record_entry.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is DevTools test code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * David Dahl <ddahl@mozilla.com>
- * Patrick Walton <pcwalton@mozilla.com>
- * Julian Viereck <jviereck@mozilla.com>
- * Mihai Sucan <mihai.sucan@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-// Tests that the recordEntry() method of the console store works.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-console.html";
-
-function test() {
- addTab(TEST_URI);
- browser.addEventListener("DOMContentLoaded", testStorageRecordEntry,
- false);
-}
-
-function testStorageRecordEntry() {
- browser.removeEventListener("DOMContentLoaded", testStorageRecordEntry,
- false);
- openConsole();
-
- let cs = HUDService.storage;
-
- cs.createDisplay("foo");
-
- var config = {
- logLevel: "network",
- message: "HumminaHummina!",
- activity: {
- stage: "barStage",
- data: "bar bar bar bar"
- }
- };
- var entry = cs.recordEntry("foo", config);
- var res = entry.id;
- ok(entry.id != null, "Entry.id is: " + res);
- ok(cs.displayIndexes["foo"].length == 1,
- "We added one entry.");
- entry = cs.getEntry(res);
- ok(entry.id > -1,
- "We got an entry through the global interface");
-
- cs.removeDisplay("foo");
- cs = null;
- finishTest();
-}
-
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_storage_record_many_entries.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is DevTools test code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * David Dahl <ddahl@mozilla.com>
- * Patrick Walton <pcwalton@mozilla.com>
- * Julian Viereck <jviereck@mozilla.com>
- * Mihai Sucan <mihai.sucan@gmail.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-// Test that the recordManyEntries() method of the console store works.
-
-const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test//browser/test-console.html";
-
-function test() {
- addTab(TEST_URI);
- browser.addEventListener("DOMContentLoaded", testStorageRecordManyEntries,
- false);
-}
-
-function testStorageRecordManyEntries() {
- browser.removeEventListener("DOMContentLoaded",
- testStorageRecordManyEntries, false);
-
- openConsole();
-
- let cs = HUDService.storage;
-
- cs.createDisplay("foo");
-
- var configArr = [];
-
- for (var i = 0; i < 1000; i++){
- let config = {
- logLevel: "network",
- message: "HumminaHummina!",
- activity: {
- stage: "barStage",
- data: "bar bar bar bar"
- }
- };
- configArr.push(config);
- }
-
- cs.recordEntries("foo", configArr);
- ok(cs.displayIndexes["foo"].length == 1000,
- "1000 entries in foo now");
-
- cs.removeDisplay("foo");
-
- finishTest();
-}
-
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -129,16 +129,17 @@
@BINPATH@/components/content_htmldoc.xpt
@BINPATH@/components/content_html.xpt
@BINPATH@/components/content_xslt.xpt
@BINPATH@/components/content_xtf.xpt
@BINPATH@/components/cookie.xpt
@BINPATH@/components/directory.xpt
@BINPATH@/components/docshell.xpt
@BINPATH@/components/dom.xpt
+@BINPATH@/components/dom_apps.xpt
@BINPATH@/components/dom_base.xpt
@BINPATH@/components/dom_battery.xpt
@BINPATH@/components/dom_canvas.xpt
@BINPATH@/components/dom_core.xpt
@BINPATH@/components/dom_css.xpt
@BINPATH@/components/dom_events.xpt
@BINPATH@/components/dom_geolocation.xpt
@BINPATH@/components/dom_notification.xpt
@@ -370,16 +371,18 @@
@BINPATH@/components/nsPrompter.manifest
@BINPATH@/components/nsPrompter.js
#ifdef MOZ_SERVICES_SYNC
@BINPATH@/components/SyncComponents.manifest
@BINPATH@/components/Weave.js
#endif
@BINPATH@/components/TelemetryPing.js
@BINPATH@/components/TelemetryPing.manifest
+@BINPATH@/components/Webapps.js
+@BINPATH@/components/Webapps.manifest
; Modules
@BINPATH@/modules/*
; Safe Browsing
@BINPATH@/components/nsSafebrowsingApplication.manifest
@BINPATH@/components/nsSafebrowsingApplication.js
@BINPATH@/components/nsURLClassifier.manifest
--- a/browser/installer/removed-files.in
+++ b/browser/installer/removed-files.in
@@ -247,16 +247,17 @@ res/fonts/mathfontCMEX10.properties
res/fonts/mathfontCMSY10.properties
res/fonts/mathfontMTExtra.properties
res/fonts/mathfontMath1.properties
res/fonts/mathfontMath2.properties
res/fonts/mathfontMath4.properties
res/fonts/mathfontPUA.properties
res/fonts/pangoFontEncoding.properties
res/forms.css
+res/full-screen-override.css
res/hiddenWindow.html
res/html.css
res/html/gopher-audio.gif
res/html/gopher-binary.gif
res/html/gopher-find.gif
res/html/gopher-image.gif
res/html/gopher-menu.gif
res/html/gopher-movie.gif
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -239,28 +239,26 @@ pu.notifyButton.label=Details…
pu.notifyButton.accesskey=D
# LOCALIZATION NOTE %S will be replaced by the short name of the application.
puNotifyText=%S has been updated
puAlertTitle=%S Updated
puAlertText=Click here for details
# Geolocation UI
-# LOCALIZATION NOTE (geolocation.shareLocation geolocation.dontShareLocation geolocation.alwaysShare geolocation.neverShare):
+# LOCALIZATION NOTE (geolocation.shareLocation geolocation.alwaysShareLocation geolocation.neverShareLocation):
#If you're having trouble with the word Share, please use Allow and Block in your language.
geolocation.shareLocation=Share Location
geolocation.shareLocation.accesskey=a
-geolocation.dontShareLocation=Don't Share
-geolocation.dontShareLocation.accesskey=o
-geolocation.alwaysShare=Always Share
-geolocation.alwaysShare.accesskey=A
-geolocation.neverShare=Never Share
-geolocation.neverShare.accesskey=N
-geolocation.siteWantsToKnow=%S wants to know your location.
-geolocation.fileWantsToKnow=The file %S wants to know your location.
+geolocation.alwaysShareLocation=Always Share Location
+geolocation.alwaysShareLocation.accesskey=A
+geolocation.neverShareLocation=Never Share Location
+geolocation.neverShareLocation.accesskey=N
+geolocation.shareWithSite=Would you like to share your location with %S?
+geolocation.shareWithFile=Would you like to share your location with the file %S?
# LOCALIZATION NOTE (geolocation.learnMore): Use the unicode ellipsis char, \u2026,
# or use "..." if \u2026 doesn't suit traditions in your locale.
geolocation.learnMore=Learn More…
# Phishing/Malware Notification Bar.
# LOCALIZATION NOTE (notAForgery, notAnAttack)
# The two button strings will never be shown at the same time, so
# it's okay for them to have the same access key
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -1954,17 +1954,17 @@ panel[dimmed="true"] {
border-left-style: solid;
border-top-left-radius: .3em;
margin-left: 1em;
}
/* Highlighter */
.highlighter-veil {
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: rgba(25, 25, 25, 0.5);
}
#highlighter-closebutton {
list-style-image: url("moz-icon://stock/gtk-close?size=menu");
margin-top: 0;
margin-bottom: 0;
}
rename from toolkit/themes/gnomestripe/global/webConsole.css
rename to browser/themes/gnomestripe/devtools/webconsole.css
--- a/toolkit/themes/gnomestripe/global/webConsole.css
+++ b/browser/themes/gnomestripe/devtools/webconsole.css
@@ -65,17 +65,17 @@
.webconsole-timestamp {
color: GrayText;
margin-top: 0;
margin-bottom: 0;
}
.hud-msg-node {
- list-style-image: url(chrome://global/skin/icons/webconsole.png);
+ list-style-image: url(chrome://browser/skin/devtools/webconsole.png);
-moz-image-region: rect(0, 1px, 0, 0);
}
.webconsole-msg-icon {
margin: 3px 4px;
width: 8px;
height: 8px;
}
@@ -157,17 +157,17 @@
.webconsole-filter-button {
margin: 0 3px;
}
.webconsole-filter-button > .toolbarbutton-menubutton-button,
.webconsole-filter-button:not([type="menu-button"]) {
-moz-box-orient: horizontal;
- list-style-image: url("chrome://global/skin/icons/webconsole.png");
+ list-style-image: url("chrome://browser/skin/devtools/webconsole.png");
}
/* Network styles */
.webconsole-filter-button[category="net"] {
-moz-image-region: rect(0, 8px, 8px, 0);
}
.webconsole-msg-network > .webconsole-msg-icon-container {
rename from toolkit/themes/gnomestripe/global/icons/webconsole.png
rename to browser/themes/gnomestripe/devtools/webconsole.png
rename from toolkit/themes/gnomestripe/global/webConsole_networkPanel.css
rename to browser/themes/gnomestripe/devtools/webconsole_networkpanel.css
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -81,16 +81,19 @@ browser.jar:
skin/classic/browser/tabview/edit-light.png (tabview/edit-light.png)
skin/classic/browser/tabview/search.png (tabview/search.png)
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/browser/devtools/arrows.png (devtools/arrows.png)
skin/classic/browser/devtools/goto-mdn.png (devtools/goto-mdn.png)
skin/classic/browser/devtools/csshtmltree.css (devtools/csshtmltree.css)
+ skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
+ skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
+ skin/classic/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/browser/devtools/gcli.css (devtools/gcli.css)
skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png)
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -2685,17 +2685,17 @@ panel[dimmed="true"] {
border-top-left-radius: .3em;
margin-left: 1em;
}
/* Highlighter */
.highlighter-veil {
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: rgba(25, 25, 25, 0.5);
}
#highlighter-closebutton {
list-style-image: url("chrome://browser/skin/devtools/toolbarbutton-close.png");
-moz-image-region: rect(0, 16px, 16px, 0);
min-width: 16px;
width: 16px;
margin: 0 4px;
rename from toolkit/themes/pinstripe/global/webConsole.css
rename to browser/themes/pinstripe/devtools/webconsole.css
--- a/toolkit/themes/pinstripe/global/webConsole.css
+++ b/browser/themes/pinstripe/devtools/webconsole.css
@@ -32,17 +32,17 @@
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
-%include shared.inc
+%include ../shared.inc
.hud-box {
border-bottom: 1px solid #aaa;
text-shadow: none;
}
.hud-box.animated {
-moz-transition: height 100ms;
@@ -68,17 +68,17 @@
.webconsole-timestamp {
color: GrayText;
margin-top: 0;
margin-bottom: 0;
}
.hud-msg-node {
- list-style-image: url(chrome://global/skin/icons/webconsole.png);
+ list-style-image: url(chrome://browser/skin/devtools/webconsole.png);
-moz-image-region: rect(0, 1px, 0, 0);
}
.webconsole-msg-icon {
margin: 3px 4px;
width: 8px;
height: 8px;
}
@@ -182,17 +182,17 @@
box-shadow: @roundButtonPressedShadow@;
background: #d0d0d0;
}
.webconsole-filter-button > .toolbarbutton-menubutton-button {
-moz-appearance: none;
margin: 0;
padding: 1px 0;
- list-style-image: url("chrome://global/skin/icons/webconsole.png");
+ list-style-image: url(chrome://browser/skin/devtools/webconsole.png);
-moz-box-orient: horizontal;
}
.webconsole-filter-button:hover:active,
.webconsole-clear-console-button:hover:active {
text-shadow: @loweredShadow@;
box-shadow: @roundButtonPressedShadow@;
background: @roundButtonPressedBackground@;
rename from toolkit/themes/pinstripe/global/icons/webconsole.png
rename to browser/themes/pinstripe/devtools/webconsole.png
rename from toolkit/themes/pinstripe/global/webConsole_networkPanel.css
rename to browser/themes/pinstripe/devtools/webconsole_networkpanel.css
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -122,16 +122,19 @@ browser.jar:
skin/classic/browser/tabview/stack-expander.png (tabview/stack-expander.png)
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/browser/devtools/arrows.png (devtools/arrows.png)
skin/classic/browser/devtools/goto-mdn.png (devtools/goto-mdn.png)
skin/classic/browser/devtools/csshtmltree.css (devtools/csshtmltree.css)
skin/classic/browser/devtools/gcli.css (devtools/gcli.css)
skin/classic/browser/devtools/toolbarbutton-close.png (devtools/toolbarbutton-close.png)
+* skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
+ skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
+ skin/classic/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle.png (devtools/breadcrumbs/ltr-middle.png)
--- a/browser/themes/winstripe/browser-aero.css
+++ b/browser/themes/winstripe/browser-aero.css
@@ -70,16 +70,21 @@
border: 0;
-moz-border-end: 1px solid #A9B7C9;
min-width: 0;
width: 3px;
background-color: transparent;
-moz-margin-start: -3px;
position: relative;
}
+
+ .menu-accel,
+ .menu-iconic-accel {
+ color: graytext;
+ }
}
@media all and (-moz-windows-compositor) {
/* These should be hidden w/ glass enabled. Windows draws its own buttons. */
.titlebar-button {
display: none;
}
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -2620,17 +2620,17 @@ panel[dimmed="true"] {
border-left-style: solid;
border-top-left-radius: .3em;
margin-left: 1em;
}
/* Highlighter */
.highlighter-veil {
- background-color: rgba(0, 0, 0, 0.5);
+ background-color: rgba(25, 25, 25, 0.5);
}
#highlighter-closebutton {
list-style-image: url("chrome://browser/skin/devtools/toolbarbutton-close.png");
-moz-image-region: rect(0, 16px, 16px, 0);
min-width: 16px;
width: 16px;
-moz-appearance: none;
rename from toolkit/themes/winstripe/global/webConsole.css
rename to browser/themes/winstripe/devtools/webconsole.css
--- a/toolkit/themes/winstripe/global/webConsole.css
+++ b/browser/themes/winstripe/devtools/webconsole.css
@@ -64,17 +64,17 @@
.webconsole-timestamp {
color: GrayText;
margin-top: 0;
margin-bottom: 0;
}
.hud-msg-node {
- list-style-image: url(chrome://global/skin/icons/webconsole.png);
+ list-style-image: url(chrome://browser/skin/devtools/webconsole.png);
-moz-image-region: rect(0, 1px, 0, 0);
}
.webconsole-msg-icon {
margin: 3px 4px;
width: 8px;
height: 8px;
}
@@ -151,17 +151,17 @@
}
.webconsole-clear-console-button > .toolbarbutton-icon {
display: none;
}
.webconsole-filter-button > .toolbarbutton-menubutton-button {
-moz-box-orient: horizontal;
- list-style-image: url("chrome://global/skin/icons/webconsole.png");
+ list-style-image: url("chrome://browser/skin/devtools/webconsole.png");
}
.webconsole-filter-button > .toolbarbutton-menubutton-button,
.webconsole-filter-button > .toolbarbutton-menubutton-button:hover:active {
-moz-padding-start: 6px;
-moz-padding-end: 3px;
}
rename from toolkit/themes/winstripe/global/icons/webconsole.png
rename to browser/themes/winstripe/devtools/webconsole.png
rename from toolkit/themes/winstripe/global/webConsole_networkPanel.css
rename to browser/themes/winstripe/devtools/webconsole_networkpanel.css
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -106,16 +106,19 @@ browser.jar:
skin/classic/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png)
skin/classic/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/browser/devtools/arrows.png (devtools/arrows.png)
skin/classic/browser/devtools/goto-mdn.png (devtools/goto-mdn.png)
skin/classic/browser/devtools/csshtmltree.css (devtools/csshtmltree.css)
skin/classic/browser/devtools/gcli.css (devtools/gcli.css)
skin/classic/browser/devtools/toolbarbutton-close.png (devtools/toolbarbutton-close.png)
+ skin/classic/browser/devtools/webconsole.css (devtools/webconsole.css)
+ skin/classic/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
+ skin/classic/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png)
skin/classic/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png)
skin/classic/browser/devtools/breadcrumbs/ltr-middle.png (devtools/breadcrumbs/ltr-middle.png)
@@ -259,16 +262,19 @@ browser.jar:
skin/classic/aero/browser/tabview/tabview.png (tabview/tabview.png)
skin/classic/aero/browser/tabview/tabview-inverted.png (tabview/tabview-inverted.png)
skin/classic/aero/browser/tabview/tabview.css (tabview/tabview.css)
skin/classic/aero/browser/devtools/arrows.png (devtools/arrows.png)
skin/classic/aero/browser/devtools/goto-mdn.png (devtools/goto-mdn.png)
skin/classic/aero/browser/devtools/csshtmltree.css (devtools/csshtmltree.css)
skin/classic/aero/browser/devtools/gcli.css (devtools/gcli.css)
skin/classic/aero/browser/devtools/toolbarbutton-close.png (devtools/toolbarbutton-close.png)
+ skin/classic/aero/browser/devtools/webconsole.css (devtools/webconsole.css)
+ skin/classic/aero/browser/devtools/webconsole_networkpanel.css (devtools/webconsole_networkpanel.css)
+ skin/classic/aero/browser/devtools/webconsole.png (devtools/webconsole.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-pressed.png (devtools/breadcrumbs/ltr-end-pressed.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png (devtools/breadcrumbs/ltr-end-selected-pressed.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-end-selected.png (devtools/breadcrumbs/ltr-end-selected.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-end.png (devtools/breadcrumbs/ltr-end.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-pressed.png (devtools/breadcrumbs/ltr-middle-pressed.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected-pressed.png (devtools/breadcrumbs/ltr-middle-selected-pressed.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle-selected.png (devtools/breadcrumbs/ltr-middle-selected.png)
skin/classic/aero/browser/devtools/breadcrumbs/ltr-middle.png (devtools/breadcrumbs/ltr-middle.png)
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -133,17 +133,18 @@ class ZipFileReader(object):
for name in self._zipfile.namelist():
self._extractname(name, path)
log = logging.getLogger()
def isURL(thing):
"""Return True if |thing| looks like a URL."""
- return urlparse(thing).scheme != ''
+ # We want to download URLs like http://... but not Windows paths like c:\...
+ return len(urlparse(thing).scheme) >= 2
def addCommonOptions(parser, defaults={}):
parser.add_option("--xre-path",
action = "store", type = "string", dest = "xrePath",
# individual scripts will set a sane default
default = None,
help = "absolute path to directory containing XRE (probably xulrunner)")
if 'SYMBOLS_PATH' not in defaults:
@@ -279,17 +280,17 @@ def getDebuggerInfo(directory, debugger,
"interactive" : getDebuggerInfo("interactive", False),
"args": getDebuggerInfo("args", "").split()
}
if debuggerArgs:
debuggerInfo["args"] = debuggerArgs.split()
if debuggerInteractive:
debuggerInfo["interactive"] = debuggerInteractive
-
+
return debuggerInfo
def dumpLeakLog(leakLogFile, filter = False):
"""Process the leak log, without parsing it.
Use this function if you want the raw log only.
Use it preferably with the |XPCOM_MEM_LEAK_LOG| environment variable.
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -46,16 +46,19 @@
#include "nsJSUtils.h"
#include "nsNetUtil.h"
#include "nsScriptLoader.h"
#include "nsIJSContextStack.h"
#include "nsIXULRuntime.h"
#include "nsIScriptError.h"
#include "nsIConsoleService.h"
#include "nsIProtocolHandler.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIJSRuntimeService.h"
+#include "xpcpublic.h"
#ifdef ANDROID
#include <android/log.h>
#endif
static bool
IsChromeProcess()
{
@@ -803,16 +806,68 @@ nsFrameScriptExecutor::LoadFrameScriptIn
JSPRINCIPALS_DROP(mCx, jsprin);
}
}
JSContext* unused;
nsContentUtils::ThreadJSContextStack()->Pop(&unused);
}
}
+bool
+nsFrameScriptExecutor::InitTabChildGlobalInternal(nsISupports* aScope)
+{
+
+ nsCOMPtr<nsIJSRuntimeService> runtimeSvc =
+ do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+ NS_ENSURE_TRUE(runtimeSvc, false);
+
+ JSRuntime* rt = nsnull;
+ runtimeSvc->GetRuntime(&rt);
+ NS_ENSURE_TRUE(rt, false);
+
+ JSContext* cx = JS_NewContext(rt, 8192);
+ NS_ENSURE_TRUE(cx, false);
+
+ mCx = cx;
+
+ nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));
+
+ JS_SetNativeStackQuota(cx, 128 * sizeof(size_t) * 1024);
+
+ JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_PRIVATE_IS_NSISUPPORTS);
+ JS_SetVersion(cx, JSVERSION_LATEST);
+ JS_SetErrorReporter(cx, ContentScriptErrorReporter);
+
+ xpc_LocalizeContext(cx);
+
+ JSAutoRequest ar(cx);
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ const PRUint32 flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES |
+ nsIXPConnect::FLAG_SYSTEM_GLOBAL_OBJECT;
+
+
+ JS_SetContextPrivate(cx, aScope);
+
+ nsresult rv =
+ xpc->InitClassesWithNewWrappedGlobal(cx, aScope,
+ NS_GET_IID(nsISupports),
+ mPrincipal, nsnull,
+ flags, getter_AddRefs(mGlobal));
+ NS_ENSURE_SUCCESS(rv, false);
+
+
+ JSObject* global = nsnull;
+ rv = mGlobal->GetJSObject(&global);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ JS_SetGlobalObject(cx, global);
+ DidCreateCx();
+ return true;
+}
+
// static
void
nsFrameScriptExecutor::Traverse(nsFrameScriptExecutor *tmp,
nsCycleCollectionTraversalCallback &cb)
{
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mGlobal)
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mCx");
nsContentUtils::XPConnect()->NoteJSContext(tmp->mCx, cb);
--- a/content/base/src/nsFrameMessageManager.h
+++ b/content/base/src/nsFrameMessageManager.h
@@ -226,16 +226,17 @@ protected:
mDelayedCxDestroy(false)
{ MOZ_COUNT_CTOR(nsFrameScriptExecutor); }
~nsFrameScriptExecutor()
{ MOZ_COUNT_DTOR(nsFrameScriptExecutor); }
void DidCreateCx();
// Call this when you want to destroy mCx.
void DestroyCx();
void LoadFrameScriptInternal(const nsAString& aURL);
+ bool InitTabChildGlobalInternal(nsISupports* aScope);
static void Traverse(nsFrameScriptExecutor *tmp,
nsCycleCollectionTraversalCallback &cb);
nsCOMPtr<nsIXPConnectJSObjectHolder> mGlobal;
JSContext* mCx;
PRUint32 mCxStackRefCnt;
bool mDelayedCxDestroy;
nsCOMPtr<nsIPrincipal> mPrincipal;
static nsDataHashtable<nsStringHashKey, nsFrameJSScriptExecutorHolder*>* sCachedScripts;
--- a/content/base/src/nsInProcessTabChildGlobal.cpp
+++ b/content/base/src/nsInProcessTabChildGlobal.cpp
@@ -288,63 +288,20 @@ nsInProcessTabChildGlobal::PreHandleEven
#endif
return NS_OK;
}
nsresult
nsInProcessTabChildGlobal::InitTabChildGlobal()
{
- nsCOMPtr<nsIJSRuntimeService> runtimeSvc =
- do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
- NS_ENSURE_STATE(runtimeSvc);
-
- JSRuntime* rt = nsnull;
- runtimeSvc->GetRuntime(&rt);
- NS_ENSURE_STATE(rt);
-
- JSContext* cx = JS_NewContext(rt, 8192);
- NS_ENSURE_STATE(cx);
-
- mCx = cx;
-
- nsContentUtils::XPConnect()->SetSecurityManagerForJSContext(cx, nsContentUtils::GetSecurityManager(), 0);
- nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));
-
- JS_SetNativeStackQuota(cx, 128 * sizeof(size_t) * 1024);
-
- JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_PRIVATE_IS_NSISUPPORTS);
- JS_SetVersion(cx, JSVERSION_LATEST);
- JS_SetErrorReporter(cx, ContentScriptErrorReporter);
-
- xpc_LocalizeContext(cx);
-
- JSAutoRequest ar(cx);
- nsIXPConnect* xpc = nsContentUtils::XPConnect();
- const PRUint32 flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES |
- /*nsIXPConnect::OMIT_COMPONENTS_OBJECT ? |*/
- nsIXPConnect::FLAG_SYSTEM_GLOBAL_OBJECT;
nsISupports* scopeSupports =
NS_ISUPPORTS_CAST(nsIDOMEventTarget*, this);
- JS_SetContextPrivate(cx, scopeSupports);
-
- nsresult rv =
- xpc->InitClassesWithNewWrappedGlobal(cx, scopeSupports,
- NS_GET_IID(nsISupports),
- GetPrincipal(), nsnull,
- flags, getter_AddRefs(mGlobal));
- NS_ENSURE_SUCCESS(rv, false);
-
- JSObject* global = nsnull;
- rv = mGlobal->GetJSObject(&global);
- NS_ENSURE_SUCCESS(rv, false);
-
- JS_SetGlobalObject(cx, global);
- DidCreateCx();
+ NS_ENSURE_STATE(InitTabChildGlobalInternal(scopeSupports));
return NS_OK;
}
class nsAsyncScriptLoad : public nsRunnable
{
public:
nsAsyncScriptLoad(nsInProcessTabChildGlobal* aTabChild, const nsAString& aURL)
: mTabChild(aTabChild), mURL(aURL) {}
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -2486,16 +2486,26 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
if (!charset.IsEmpty()) {
nsCAutoString specifiedCharset;
bool haveCharset;
PRInt32 charsetStart, charsetEnd;
rv = NS_ExtractCharsetFromContentType(contentType, specifiedCharset,
&haveCharset, &charsetStart,
&charsetEnd);
if (NS_SUCCEEDED(rv)) {
+ // special case: the extracted charset is quoted with single quotes
+ // -- for the purpose of preserving what was set we want to handle
+ // them as delimiters (although they aren't really)
+ if (specifiedCharset.Length() >= 2 &&
+ specifiedCharset.First() == '\'' &&
+ specifiedCharset.Last() == '\'') {
+ specifiedCharset = Substring(specifiedCharset, 1,
+ specifiedCharset.Length() - 2);
+ }
+
// If the content-type the page set already has a charset parameter,
// and it's the same charset, up to case, as |charset|, just send the
// page-set content-type header. Apparently at least
// google-web-toolkit is broken and relies on the exact case of its
// charset parameter, which makes things break if we use |charset|
// (which is always a fully resolved charset per our charset alias
// table, hence might be differently cased).
if (!specifiedCharset.Equals(charset,
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -2059,16 +2059,20 @@ void nsHTMLMediaElement::ProcessMediaFra
void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
{
mChannels = aChannels;
mRate = aRate;
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
+ if (!mBegun) {
+ // Something ended our downloaded. We're probably done with downloading already.
+ ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
+ }
if (mDecoder && mDecoder->IsSeekable()) {
ProcessMediaFragmentURI();
mDecoder->SetEndTime(mFragmentEnd);
}
}
void nsHTMLMediaElement::FirstFrameLoaded(bool aResourceFullyLoaded)
{
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -1288,22 +1288,20 @@ nsHTMLDocument::GetURL(nsAString& aURL)
}
CopyUTF8toUTF16(str, aURL);
return NS_OK;
}
nsIContent*
-nsHTMLDocument::GetBody(nsresult *aResult)
+nsHTMLDocument::GetBody()
{
Element* body = GetBodyElement();
- *aResult = NS_OK;
-
if (body) {
// There is a body element, return that as the body.
return body;
}
// The document is most likely a frameset document so look for the
// outer most frameset element
nsRefPtr<nsContentList> nodeList =
@@ -1312,20 +1310,19 @@ nsHTMLDocument::GetBody(nsresult *aResul
return nodeList->GetNodeAt(0);
}
NS_IMETHODIMP
nsHTMLDocument::GetBody(nsIDOMHTMLElement** aBody)
{
*aBody = nsnull;
- nsresult rv;
- nsIContent *body = GetBody(&rv);
-
- return body ? CallQueryInterface(body, aBody) : rv;
+ nsIContent *body = GetBody();
+
+ return body ? CallQueryInterface(body, aBody) : NS_OK;
}
NS_IMETHODIMP
nsHTMLDocument::SetBody(nsIDOMHTMLElement* aBody)
{
nsCOMPtr<nsIContent> newBody = do_QueryInterface(aBody);
Element* root = GetRootElement();
--- a/content/html/document/src/nsHTMLDocument.h
+++ b/content/html/document/src/nsHTMLDocument.h
@@ -130,17 +130,18 @@ public:
* Returns the result of document.all[aID] which can either be a node
* or a nodelist depending on if there are multiple nodes with the same
* id.
*/
nsISupports *GetDocumentAllResult(const nsAString& aID,
nsWrapperCache **aCache,
nsresult *aResult);
- nsIContent *GetBody(nsresult *aResult);
+ nsIContent *GetBody();
+ Element *GetHead() { return GetHeadElement(); }
already_AddRefed<nsContentList> GetElementsByName(const nsAString & aName)
{
return NS_GetFuncStringContentList(this, MatchNameAttribute, nsnull,
UseExistingNameString, aName);
}
virtual nsresult ResolveName(const nsAString& aName,
--- a/content/media/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -347,31 +347,31 @@ function MediaTestManager() {
this.nextTest();
}
// Registers that the test corresponding to 'token' has been started.
// Don't call more than once per token.
this.started = function(token) {
this.tokens.push(token);
this.numTestsRunning++;
- is(this.numTestsRunning, this.tokens.length, "[started] Length of array should match number of running tests");
+ is(this.numTestsRunning, this.tokens.length, "[started " + token + "] Length of array should match number of running tests");
}
// Registers that the test corresponding to 'token' has finished. Call when
// you've finished your test. If all tests are complete this will finish the
// run, otherwise it may start up the next run. It's ok to call multiple times
// per token.
this.finished = function(token) {
var i = this.tokens.indexOf(token);
if (i != -1) {
// Remove the element from the list of running tests.
this.tokens.splice(i, 1);
}
this.numTestsRunning--;
- is(this.numTestsRunning, this.tokens.length, "[finished] Length of array should match number of running tests");
+ is(this.numTestsRunning, this.tokens.length, "[finished " + token + "] Length of array should match number of running tests");
if (this.tokens.length < PARALLEL_TESTS) {
this.nextTest();
}
}
// Starts the next batch of tests, or finishes if they're all done.
// Don't call this directly, call finished(token) when you're done.
this.nextTest = function() {
--- a/content/media/test/test_preload_actions.html
+++ b/content/media/test/test_preload_actions.html
@@ -40,51 +40,52 @@ var finished = false;
addLoadEvent(function() {gotLoadEvent=true;});
function log(m) {
var l = document.getElementById("log");
l.innerHTML += m;
}
function maybeFinish(v, n) {
+ if (v._finished) {
+ return;
+ }
+ v._finished = true;
log(n + ",");
if (v.parentNode) {
v.parentNode.removeChild(v);
}
manager.finished(v.token);
}
function filename(uri) {
return uri.substr(uri.lastIndexOf("/")+1);
}
-// Every test must have a setup(v) function, and must set _finished field on target v to
-// true when test is complete.
+// Every test must have a setup(v) function, and must call maybeFinish() when test is complete.
var tests = [
{
// 1. Add preload:none video with src to document. Load should halt at NETWORK_IDLE and HAVE_NOTHING,
// after receiving a suspend event. Should not receive loaded events until after we call load().
// Note the suspend event is explictly sent by our "stop the load" code, but other tests can't rely
// on it for the preload:metadata case, as there can be multiple suspend events when loading metadata.
suspend:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(1) Must get loadstart.");
is(v._gotLoadedMetaData, false, "(1) Must not get loadedmetadata.");
is(v.readyState, v.HAVE_NOTHING, "(1) ReadyState must be HAVE_NOTHING");
is(v.networkState, v.NETWORK_IDLE, "(1) NetworkState must be NETWORK_IDLE");
- v._finished = true;
maybeFinish(v, 1);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("suspend", this.suspend, false);
v.src = test.name;
document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
},
},
@@ -93,50 +94,46 @@ var tests = [
// after suspend event and after loadedmetadata.
loadeddata:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(2) Must get loadstart.");
is(v._gotLoadedMetaData, true, "(2) Must get loadedmetadata.");
ok(v.readyState >= v.HAVE_CURRENT_DATA, "(2) ReadyState must be >= HAVE_CURRENT_DATA");
is(v.networkState, v.NETWORK_IDLE, "(2) NetworkState must be NETWORK_IDLE");
- v._finished = true;
maybeFinish(v, 2);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "metadata";
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadeddata", this.loadeddata, false);
v.src = test.name;
document.body.appendChild(v); // Causes implicit load, which will be halted after
// metadata due to preload:metadata.
},
},
{
// 3. Add preload:auto to document. Should receive canplaythrough eventually.
canplaythrough:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(3) Must get loadstart.");
is(v._gotLoadedMetaData, true, "(3) Must get loadedmetadata.");
- v._finished = true;
maybeFinish(v, 3);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "auto";
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("canplaythrough", this.canplaythrough, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},
@@ -154,26 +151,24 @@ var tests = [
is(v.readyState, v.HAVE_NOTHING, "(4) ReadyState must be HAVE_NOTHING");
is(v.networkState, v.NETWORK_IDLE, "(4) NetworkState must be NETWORK_IDLE");
v.play(); // Should load and play through.
},
ended:
function(e) {
ok(true, "(4) Got playback ended");
- e.target._finished = true;
maybeFinish(e.target, 4);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
v._gotSuspend = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("suspend", this.suspend, false);
v.addEventListener("ended", this.ended, false);
v.src = test.name;
document.body.appendChild(v);
},
@@ -183,25 +178,23 @@ var tests = [
// preload:none load. Add a src, it shouldn't load.
suspend:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(5) Must get loadstart.");
is(v._gotLoadedMetaData, false, "(5) Must not get loadedmetadata.");
is(v.readyState, v.HAVE_NOTHING, "(5) ReadyState must be HAVE_NOTHING");
is(v.networkState, v.NETWORK_IDLE, "(5) NetworkState must be NETWORK_IDLE");
- v._finished = true;
maybeFinish(v, 5);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("suspend", this.suspend, false);
document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
v.src = test.name; // Load should start, and halt at preload:none.
},
},
@@ -210,25 +203,23 @@ var tests = [
// preload:none load. Add a source, it shouldn't load.
suspend:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(6) Must get loadstart.");
is(v._gotLoadedMetaData, false, "(6) Must not get loadedmetadata.");
is(v.readyState, v.HAVE_NOTHING, "(6) ReadyState must be HAVE_NOTHING");
is(v.networkState, v.NETWORK_IDLE, "(6) NetworkState must be NETWORK_IDLE");
- v._finished = true;
maybeFinish(v, 6);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("suspend", this.suspend, false);
document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
var s = document.createElement("source");
s.src = test.name;
s.type = test.type;
@@ -250,26 +241,24 @@ var tests = [
is(v.networkState, v.NETWORK_IDLE, "(7) NetworkState must be NETWORK_IDLE");
v.play(); // Should load and play through.
},
ended:
function(e) {
ok(true, "(7) Got playback ended");
var v = e.target;
- v._finished = true;
is(v._gotErrorEvent, true, "(7) Should get error event from first source load failure");
maybeFinish(v, 7);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v._gotErrorEvent = false;
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("suspend", this.suspend, false);
v.addEventListener("ended", this.ended, false);
var s1 = document.createElement("source");
s1.src = "not-a-real-file.404"
@@ -286,124 +275,112 @@ var tests = [
{
// 8. Change preload value from none to metadata should cause metadata to be loaded.
loadeddata:
function(e) {
var v = e.target;
is(v._gotLoadedMetaData, true, "(8) Must get loadedmetadata.");
ok(v.readyState >= v.HAVE_CURRENT_DATA, "(8) ReadyState must be >= HAVE_CURRENT_DATA on suspend.");
is(v.networkState, v.NETWORK_IDLE, "(8) NetworkState must be NETWORK_IDLE when load is halted");
- v._finished = true;
maybeFinish(v, 8);
},
setup:
function(v) {
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadstart", function(e){v.preload = "metadata";}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadeddata", this.loadeddata, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},
/*{
// 9. Change preload value from metadata to auto should cause entire media to be loaded.
// For some reason we don't always receive the canplaythrough event, particuarly on this test.
// We've disabled this test until bug 568402 is fixed.
canplaythrough:
function(e) {
var v = e.target;
- if (v._finished)
- return;
is(v._gotLoadStart, true, "(9) Must get loadstart.");
is(v._gotLoadedMetaData, true, "(9) Must get loadedmetadata.");
- v._finished = true;
maybeFinish(v, 9);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "metadata";
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadeddata", function(){v.preload = "auto"}, false);
v.addEventListener("canplaythrough", this.canplaythrough, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},*/
{
// 10. Change preload value from none to auto should cause entire media to be loaded.
canplaythrough:
function(e) {
var v = e.target;
is(v._gotLoadedMetaData, true, "(10) Must get loadedmetadata.");
- v._finished = true;
maybeFinish(v, 10);
},
setup:
function(v) {
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadstart", function(e){v.preload = "auto";}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("canplaythrough", this.canplaythrough, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},
{
// 11. Change preload value from none to metadata should cause metadata to load.
loadeddata:
function(e) {
var v = e.target;
is(v._gotLoadedMetaData, true, "(11) Must get loadedmetadata.");
ok(v.readyState >= v.HAVE_CURRENT_DATA, "(11) ReadyState must be >= HAVE_CURRENT_DATA.");
is(v.networkState, v.NETWORK_IDLE, "(11) NetworkState must be NETWORK_IDLE.");
- v._finished = true;
maybeFinish(v, 11);
},
setup:
function(v) {
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.addEventListener("loadstart", function(e){v.preload = "metadata";}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadeddata", this.loadeddata, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},
{
// 12. Change preload value from auto to metadata after load started,
// should still do full load, should not halt after metadata only.
canplaythrough:
function(e) {
var v = e.target;
is(v._gotLoadedMetaData, true, "(12) Must get loadedmetadata.");
is(v._gotLoadStart, true, "(12) Must get loadstart.");
- v._finished = true;
maybeFinish(v, 12);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "auto";
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("canplaythrough", this.canplaythrough, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
v.preload = "metadata";
},
@@ -414,25 +391,23 @@ var tests = [
// should still load up to metadata, should not halt immediately.
loadeddata:
function(e) {
var v = e.target;
is(v._gotLoadStart, true, "(13) Must get loadstart.");
is(v._gotLoadedMetaData, true, "(13) Must get loadedmetadata.");
ok(v.readyState >= v.HAVE_CURRENT_DATA, "(13) ReadyState must be >= HAVE_CURRENT_DATA.");
is(v.networkState, v.NETWORK_IDLE, "(13) NetworkState must be NETWORK_IDLE.");
- v._finished = true;
maybeFinish(v, 13);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "metadata";
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("loadeddata", this.loadeddata, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
v.preload = "none";
},
@@ -448,114 +423,104 @@ var tests = [
is(v.networkState, v.NETWORK_IDLE, "(14) NetworkState must be NETWORK_IDLE");
v.play();
},
ended:
function(e) {
ok(true, "(14) Got playback ended");
var v = e.target;
- v._finished = true;
maybeFinish(v, 14);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "metadata";
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("ended", this.ended, false);
v.addEventListener("loadeddata", this.loadeddata, false);
v.src = test.name;
document.body.appendChild(v); // Causes implicit load, which will be halted after
// metadata due to preload:metadata.
},
},
{
// 15. Autoplay should override preload:none.
ended:
function(e) {
ok(true, "(15) Got playback ended.");
var v = e.target;
- v._finished = true;
maybeFinish(v, 15);
},
setup:
function(v) {
v._gotLoadStart = false;
v._gotLoadedMetaData = false;
- v._finished = false;
v.preload = "none";
v.autoplay = true;
v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
v.addEventListener("ended", this.ended, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},
{
// 16. Autoplay should override preload:metadata.
ended:
function(e) {
ok(true, "(16) Got playback ended.");
var v = e.target;
- v._finished = true;
maybeFinish(v, 16);
},
setup:
function(v) {
- v._finished = false;
v.preload = "metadata";
v.autoplay = true;
v.addEventListener("ended", this.ended, false);
v.src = test.name; // Causes implicit load.
document.body.appendChild(v);
},
},
{
// 17. On a preload:none video, adding autoplay should disable preload none, i.e. don't break autoplay!
ended:
function(e) {
ok(true, "(17) Got playback ended.");
var v = e.target;
- v._finished = true;
maybeFinish(v, 17);
},
setup:
function(v) {
v.addEventListener("ended", this.ended, false);
- v._finished = false;
v.preload = "none";
document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
v.autoplay = true;
v.src = test.name;
},
},
{
// 18. On a preload='none' video, call play() before load algorithms's sync
// has run, the play() call should override preload='none'.
ended:
function(e) {
ok(true, "(18) Got playback ended.");
var v = e.target;
- v._finished = true;
maybeFinish(v, 18);
},
setup:
function(v) {
v.addEventListener("ended", this.ended, false);
- v._finished = false;
v.preload = "none";
v.src = test.name; // Schedules async section to continue load algorithm.
document.body.appendChild(v);
v.play(); // Should cause preload:none to be overridden.
},
}
];
--- a/content/svg/content/src/nsSVGPathElement.cpp
+++ b/content/svg/content/src/nsSVGPathElement.cpp
@@ -434,17 +434,17 @@ nsSVGPathElement::GetPathLengthScale(Pat
if (mPathLength.IsExplicitlySet()) {
float authorsPathLengthEstimate = mPathLength.GetAnimValue();
if (authorsPathLengthEstimate > 0) {
gfxMatrix matrix;
if (aFor == eForTextPath) {
// For textPath, a transform on the referenced path affects the
// textPath layout, so when calculating the actual path length
// we need to take that into account.
- matrix = PrependLocalTransformTo(gfxMatrix());
+ matrix = PrependLocalTransformTo(matrix);
}
nsRefPtr<gfxFlattenedPath> path = GetFlattenedPath(matrix);
if (path) {
return path->GetLength() / authorsPathLengthEstimate;
}
}
}
return 1.0;
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -61,16 +61,17 @@ DIRS = \
interfaces/xul \
interfaces/storage \
interfaces/json \
interfaces/offline \
interfaces/geolocation \
interfaces/notification \
interfaces/svg \
interfaces/smil \
+ interfaces/apps \
$(NULL)
DIRS += \
base \
battery \
sms \
src \
locales \
--- a/dom/base/Makefile.in
+++ b/dom/base/Makefile.in
@@ -47,17 +47,23 @@ LIBRARY_NAME = jsdombase_s
LIBXUL_LIBRARY = 1
FORCE_STATIC_LIB = 1
EXTRA_PP_COMPONENTS = \
ConsoleAPI.js \
ConsoleAPI.manifest \
$(NULL)
+EXTRA_COMPONENTS = \
+ Webapps.js \
+ Webapps.manifest \
+ $(NULL)
+
EXTRA_JS_MODULES = ConsoleAPIStorage.jsm \
+ Webapps.jsm \
$(NULL)
XPIDLSRCS = \
nsIEntropyCollector.idl \
nsIScriptChannel.idl \
$(NULL)
EXPORTS = \
new file mode 100644
--- /dev/null
+++ b/dom/base/Webapps.js
@@ -0,0 +1,346 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Open Web Apps.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabrice Desré <fabrice@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function WebappsRegistry() {
+ this.messages = ["Webapps:Install:Return:OK", "Webapps:Install:Return:KO",
+ "Webapps:Uninstall:Return:OK", "Webapps:Uninstall:Return:KO",
+ "Webapps:Enumerate:Return:OK", "Webapps:Enumerate:Return:KO"];
+
+ this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+
+ this.messages.forEach((function(msgName) {
+ this.mm.addMessageListener(msgName, this);
+ }).bind(this));
+
+ this._window = null;
+ this._id = this._getRandomId();
+ this._callbacks = [];
+}
+
+WebappsRegistry.prototype = {
+ _onerror: null,
+ _oninstall: null,
+ _onuninstall: null,
+
+ /** from https://developer.mozilla.org/en/OpenWebApps/The_Manifest
+ * only the name property is mandatory
+ */
+ checkManifest: function(aManifest, aInstallOrigin) {
+ // TODO : check for install_allowed_from
+ if (aManifest.name == undefined)
+ return false;
+
+ if (aManifest.installs_allowed_from) {
+ ok = false;
+ aManifest.installs_allowed_from.forEach(function(aOrigin) {
+ if (aOrigin == "*" || aOrigin == aInstallOrigin)
+ ok = true;
+ });
+ return ok;
+ }
+ return true;
+ },
+
+ getCallbackId: function(aCallback) {
+ let id = "id" + this._getRandomId();
+ this._callbacks[id] = aCallback;
+ return id;
+ },
+
+ getCallback: function(aId) {
+ return this._callbacks[aId];
+ },
+
+ removeCallback: function(aId) {
+ if (this._callbacks[aId])
+ delete this._callbacks[aId];
+ },
+
+ _getRandomId: function() {
+ return Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+ },
+
+ _convertAppsArray: function(aApps) {
+ let apps = new Array();
+ for (let i = 0; i < aApps.length; i++) {
+ let app = aApps[i];
+ apps.push(new WebappsApplication(app.origin, app.manifest, app.receipt, app.installOrigin, app.installTime));
+ }
+ return apps;
+ },
+
+ set oninstall(aCallback) {
+ if (this.hasPrivileges)
+ this._oninstall = aCallback;
+ else
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ set onuninstall(aCallback) {
+ if (this.hasPrivileges)
+ this._onuninstall = aCallback;
+ else
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ set onerror(aCallback) {
+ this._onerror = aCallback;
+ },
+
+ receiveMessage: function(aMessage) {
+ let msg = aMessage.json;
+ if (!(msg.oid == this._id || aMessage.name == "Webapps:Install:Return:OK" || aMessage.name == "Webapps:Uninstall:Return:OK"))
+ return
+ let app = msg.app;
+ let cb;
+ switch (aMessage.name) {
+ case "Webapps:Install:Return:OK":
+ if (this._oninstall)
+ this._oninstall.handleEvent(new WebappsApplication(app.origin, app.manifest, app.receipt,
+ app.installOrigin, app.installTime));
+ break;
+ case "Webapps:Install:Return:KO":
+ if (this._onerror)
+ this._onerror.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.DENIED));
+ break;
+ case "Webapps:Uninstall:Return:OK":
+ if (this._onuninstall)
+ this._onuninstall.handleEvent(new WebappsApplication(msg.origin, null, null, null, 0));
+ break;
+ case "Webapps:Uninstall:Return:KO":
+ if (this._onerror)
+ this._onerror.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.PERMISSION_DENIED));
+ break;
+ case "Webapps:Enumerate:Return:OK":
+ cb = this.getCallback(msg.callbackID);
+ if (cb.success) {
+ let apps = this._convertAppsArray(msg.apps);
+ cb.success.handleEvent(apps, apps.length);
+ }
+ break;
+ case "Webapps:Enumerate:Return:KO":
+ cb = this.getCallback(msg.callbackID);
+ if (cb.error)
+ cb.error.handleEvent(new RegistryError(Ci.mozIDOMApplicationRegistryError.PERMISSION_DENIED));
+ break;
+ }
+ this.removeCallback(msg.callbackID);
+ },
+
+ _fireError: function(aCode) {
+ if (!this._onerror)
+ return;
+ this._onerror.handleEvent(new RegistryError(aCode));
+ },
+
+ _getOrigin: function(aURL) {
+ let uri = Services.io.newURI(aURL, null, null);
+ return uri.prePath;
+ },
+
+ // mozIDOMApplicationRegistry implementation
+
+ install: function(aURL, aReceipt) {
+ let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
+ xhr.open("GET", aURL, true);
+
+ xhr.addEventListener("load", (function() {
+ if (xhr.status == 200) {
+ try {
+ let installOrigin = this._getOrigin(this._window.location.href);
+ let manifest = JSON.parse(xhr.responseText, installOrigin);
+ if (!this.checkManifest(manifest, installOrigin)) {
+ this._fireError(Ci.mozIDOMApplicationRegistryError.INVALID_MANIFEST);
+ } else {
+ this.mm.sendAsyncMessage("Webapps:Install", { app: { installOrigin: installOrigin,
+ origin: this._getOrigin(aURL),
+ manifest: manifest,
+ receipt: aReceipt },
+ from: this._window.location.href,
+ oid: this._id });
+ }
+ } catch(e) {
+ this._fireError(Ci.mozIDOMApplicationRegistryError.MANIFEST_PARSE_ERROR);
+ }
+ }
+ else {
+ this._fireError(Ci.mozIDOMApplicationRegistryError.MANIFEST_URL_ERROR);
+ }
+ }).bind(this), false);
+
+ xhr.addEventListener("error", (function() {
+ this._fireError(Ci.mozIDOMApplicationRegistryError.NETWORK_ERROR);
+ }).bind(this), false);
+
+ xhr.send(null);
+ },
+
+ uninstall: function(aOrigin) {
+ if (this.hasPrivileges)
+ this.mm.sendAsyncMessage("Webapps:Uninstall", { from: this._window.location.href,
+ origin: aOrigin,
+ oid: this._id });
+ else
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ launch: function(aOrigin) {
+ this.mm.sendAsyncMessage("Webapps:Launch", { origin: aOrigin,
+ from: this._window.location.href});
+ },
+
+ enumerate: function(aSuccess, aError) {
+ this.mm.sendAsyncMessage("Webapps:Enumerate", { from: this._window.location.href,
+ origin: this._getOrigin(this._window.location.href),
+ oid: this._id,
+ callbackID: this.getCallbackId({ success: aSuccess, error: aError }) });
+ },
+
+ handleEvent: function(aEvent) {
+ if (aEvent.type == "unload") {
+ // remove all callbacks and event handlers so we don't call anything on a cleared scope
+ try {
+ this._oninstall = null;
+ this._onuninstall = null;
+ this._onerror = null;
+ this._callbacks = [];
+ } catch(e) {
+ dump("WebappsRegistry error:" + e + "\n");
+ }
+ }
+ },
+
+ // nsIDOMGlobalPropertyInitializer implementation
+ init: function(aWindow) {
+ dump("DOMApplicationRegistry::init() " + aWindow + "\n");
+ this._window = aWindow;
+ this._window.addEventListener("unload", this, false);
+ this._window.appId = this._id;
+ let from = Services.io.newURI(this._window.location.href, null, null);
+ let perm = Services.perms.testExactPermission(from, "webapps-manage");
+
+ //only pages with perm set and chrome or about pages can uninstall, enumerate all set oninstall an onuninstall
+ this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION || from.schemeIs("chrome") || from.schemeIs("about");
+ },
+
+ classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationRegistry, Ci.nsIDOMGlobalPropertyInitializer]),
+
+ classInfo: XPCOMUtils.generateCI({classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
+ contractID: "@mozilla.org/webapps;1",
+ interfaces: [Ci.mozIDOMApplicationRegistry],
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ classDescription: "Webapps Registry"})
+}
+
+function WebappsApplication(aOrigin, aManifest, aReceipt, aInstallOrigin, aInstallTime) {
+ this._origin = aOrigin;
+ this._manifest = aManifest;
+ this._receipt = aReceipt;
+ this._installOrigin = aInstallOrigin;
+ this._installTime = aInstallTime;
+}
+
+WebappsApplication.prototype = {
+ _origin: null,
+ _manifest: null,
+ _receipt: null,
+ _installOrigin: null,
+ _installTime: 0,
+
+ get origin() {
+ return this._origin;
+ },
+
+ get manifest() {
+ return this._manifest;
+ },
+
+ get receipt() {
+ return this._receipt;
+ },
+
+ get installOrigin() {
+ return this._installOrigin;
+ },
+
+ get installTime() {
+ return this._installTime;
+ },
+
+ classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication]),
+
+ classInfo: XPCOMUtils.generateCI({classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
+ contractID: "@mozilla.org/webapps/application;1",
+ interfaces: [Ci.mozIDOMApplication],
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ classDescription: "Webapps Application"})
+}
+
+function RegistryError(aCode) {
+ this._code = aCode;
+}
+
+RegistryError.prototype = {
+ _code: null,
+
+ get code() {
+ return this._code;
+ },
+
+ classID: Components.ID("{b4937718-11a3-400b-a69f-ab442a418569}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationRegistryError]),
+
+ classInfo: XPCOMUtils.generateCI({classID: Components.ID("{b4937718-11a3-400b-a69f-ab442a418569}"),
+ contractID: "@mozilla.org/webapps/error;1",
+ interfaces: [Ci.mozIDOMApplicationRegistryError],
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ classDescription: "Webapps Registry Error"})
+}
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry, WebappsApplication, RegistryError]);
new file mode 100644
--- /dev/null
+++ b/dom/base/Webapps.jsm
@@ -0,0 +1,380 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Mobile Browser.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Fabrice Desré <fabrice@mozilla.com>
+ * Mark Finkle <mfinkle@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "DOMApplicationManifest"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
+ Cu.import("resource://gre/modules/NetUtil.jsm");
+ return NetUtil;
+});
+
+let DOMApplicationRegistry = {
+ appsDir: null,
+ appsFile: null,
+ webapps: { },
+
+ init: function() {
+ this.mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+ let messages = ["Webapps:Install", "Webapps:Uninstall",
+ "Webapps:Enumerate", "Webapps:Launch"];
+
+ messages.forEach((function(msgName) {
+ this.mm.addMessageListener(msgName, this);
+ }).bind(this));
+
+ let file = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
+ file.append("webapps");
+ if (!file.exists() || !file.isDirectory()) {
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
+ }
+ this.appsDir = file;
+ this.appsFile = file.clone();
+ this.appsFile.append("webapps.json");
+ if (!this.appsFile.exists())
+ return;
+
+ try {
+ let channel = NetUtil.newChannel(this.appsFile);
+ channel.contentType = "application/json";
+ let self = this;
+ NetUtil.asyncFetch(channel, function(aStream, aResult) {
+ if (!Components.isSuccessCode(aResult)) {
+ Cu.reportError("DOMApplicationRegistry: Could not read from json file " + this.appsFile.path);
+ return;
+ }
+
+ // Read json file into a string
+ let data = null;
+ try {
+ self.webapps = JSON.parse(NetUtil.readInputStreamToString(aStream, aStream.available()) || "");
+ aStream.close();
+ } catch (ex) {
+ Cu.reportError("DOMApplicationRegistry: Could not parse JSON: " + ex);
+ }
+ });
+ } catch (ex) {
+ Cu.reportError("DOMApplicationRegistry: Could not read from " + aFile.path + " : " + ex);
+ }
+ },
+
+ receiveMessage: function(aMessage) {
+ let msg = aMessage.json;
+ let from = Services.io.newURI(msg.from, null, null);
+ let perm = Services.perms.testExactPermission(from, "webapps-manage");
+
+ //only pages with perm set and chrome or about pages can uninstall, enumerate all set oninstall an onuninstall
+ let hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION || from.schemeIs("chrome") || from.schemeIs("about");
+
+ switch (aMessage.name) {
+ case "Webapps:Install":
+ // always ask for UI to install
+ Services.obs.notifyObservers(this, "webapps-ask-install", JSON.stringify(msg));
+ break;
+ case "Webapps:Uninstall":
+ if (hasPrivileges)
+ this.uninstall(msg);
+ break;
+ case "Webapps:Launch":
+ Services.obs.notifyObservers(this, "webapps-launch", JSON.stringify(msg));
+ break;
+ case "Webapps:Enumerate":
+ if (hasPrivileges)
+ this.enumerateAll(msg)
+ else
+ this.enumerate(msg);
+ break;
+ }
+ },
+
+ _writeFile: function ss_writeFile(aFile, aData, aCallbak) {
+ // Initialize the file output stream.
+ let ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(aFile, 0x02 | 0x08 | 0x20, 0600, ostream.DEFER_OPEN);
+
+ // Obtain a converter to convert our data to a UTF-8 encoded input stream.
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ // Asynchronously copy the data to the file.
+ let istream = converter.convertToInputStream(aData);
+ NetUtil.asyncCopy(istream, ostream, function(rc) {
+ if (aCallbak)
+ aCallbak();
+ });
+ },
+
+ // clones a app object, without the manifest
+ _cloneAppObject: function(aApp) {
+ let clone = {
+ installOrigin: aApp.installOrigin,
+ origin: aApp.origin,
+ receipt: aApp.receipt,
+ installTime: aApp.installTime
+ };
+ return clone;
+ },
+
+ denyInstall: function(aData) {
+ this.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
+ },
+
+ confirmInstall: function(aData) {
+ let app = aData.app;
+ let id = this._appId(app.origin);
+
+ // install an application again is considered as an update
+ if (id) {
+ let dir = this.appsDir.clone();
+ dir.append(id);
+ try {
+ dir.remove(true);
+ } catch(e) {
+ }
+ }
+ else {
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+ id = uuidGenerator.generateUUID().toString();
+ }
+
+ let dir = this.appsDir.clone();
+ dir.append(id);
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
+
+ let manFile = dir.clone();
+ manFile.append("manifest.json");
+ this._writeFile(manFile, JSON.stringify(app.manifest));
+
+ this.webapps[id] = this._cloneAppObject(app);
+ delete this.webapps[id].manifest;
+ this.webapps[id].installTime = (new Date()).getTime()
+
+ this._writeFile(this.appsFile, JSON.stringify(this.webapps), (function() {
+ this.mm.sendAsyncMessage("Webapps:Install:Return:OK", aData);
+ }).bind(this));
+ },
+
+ _appId: function(aURI) {
+ for (let id in this.webapps) {
+ if (this.webapps[id].origin == aURI)
+ return id;
+ }
+ return null;
+ },
+
+ _readManifest: function(aId) {
+ let file = this.appsDir.clone();
+ file.append(aId);
+ file.append("manifest.json");
+ let data = "";
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
+ fstream.init(file, -1, 0, 0);
+ cstream.init(fstream, "UTF-8", 0, 0);
+ let (str = {}) {
+ let read = 0;
+ do {
+ read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
+ data += str.value;
+ } while (read != 0);
+ }
+ cstream.close(); // this closes fstream
+ try {
+ return JSON.parse(data);
+ } catch(e) {
+ return null;
+ }
+ },
+
+ uninstall: function(aData) {
+ for (let id in this.webapps) {
+ let app = this.webapps[id];
+ if (app.origin == aData.origin) {
+ delete this.webapps[id];
+ this._writeFile(this.appsFile, JSON.stringify(this.webapps));
+ let dir = this.appsDir.clone();
+ dir.append(id);
+ try {
+ dir.remove(true);
+ } catch (e) {
+ }
+ this.mm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
+ }
+ }
+ },
+
+ enumerate: function(aData) {
+ aData.apps = [];
+
+ let id = this._appId(aData.origin);
+ // if it's an app, add itself to the result
+ if (id) {
+ let app = this._cloneAppObject(this.webapps[id]);
+ app.manifest = this._readManifest(id);
+ aData.apps.push(app);
+ }
+
+ // check if it's a store.
+ let isStore = false;
+ for (id in this.webapps) {
+ let app = this._cloneAppObject(this.webapps[id]);
+ if (app.installOrigin == aData.origin) {
+ isStore = true;
+ break;
+ }
+ }
+
+ // add all the apps from this store
+ if (isStore) {
+ for (id in this.webapps) {
+ let app = this._cloneAppObject(this.webapps[id]);
+ if (app.installOrigin == aData.origin) {
+ app.manifest = this._readManifest(id);
+ aData.apps.push(app);
+ }
+ }
+ }
+
+ this.mm.sendAsyncMessage("Webapps:Enumerate:Return:OK", aData);
+ },
+
+ denyEnumerate: function(aData) {
+ this.mm.sendAsyncMessage("Webapps:Enumerate:Return:KO", aData);
+ },
+
+ enumerateAll: function(aData) {
+ aData.apps = [];
+
+ for (id in this.webapps) {
+ let app = this._cloneAppObject(this.webapps[id]);
+ app.manifest = this._readManifest(id);
+ aData.apps.push(app);
+ }
+
+ this.mm.sendAsyncMessage("Webapps:Enumerate:Return:OK", aData);
+ },
+
+ getManifestFor: function(aOrigin) {
+ let id = this._appId(aOrigin);
+ if (!id)
+ return null;
+ return this._readManifest(id);
+ }
+};
+
+/**
+ * Helper object to access manifest information with locale support
+ */
+DOMApplicationManifest = function(aManifest, aOrigin) {
+ this._origin = Services.io.newURI(aOrigin, null, null);
+ this._manifest = aManifest;
+ let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
+ .QueryInterface(Ci.nsIToolkitChromeRegistry);
+ let locale = chrome.getSelectedLocale("browser").toLowerCase();
+ this._localeRoot = this._manifest;
+
+ if (this._manifest.locales && this._manifest.locales[locale]) {
+ this._localeRoot = this._manifest.locales[locale];
+ }
+ else if (this._manifest.locales) {
+ // try with the language part of the locale ("en" for en-GB) only
+ let lang = locale.split('-')[0];
+ if (land != locale && this._manifest.locales[lang])
+ this._localeRoot = this._manifest.locales[lang];
+ }
+}
+
+DOMApplicationManifest.prototype = {
+ _localeProp: function(aProp) {
+ if (this._localeRoot[aProp] != undefined)
+ return this._localeRoot[aProp];
+ return this._manifest[aProp];
+ },
+
+ get name() {
+ return this._localeProp("name");
+ },
+
+ get description() {
+ return this._localeProp("description");
+ },
+
+ get version() {
+ return this._localeProp("version");
+ },
+
+ get launch_path() {
+ return this._localeProp("launch_path");
+ },
+
+ get developer() {
+ return this._localeProp("developer");
+ },
+
+ get icons() {
+ return this._localeProp("icons");
+ },
+
+ iconURLForSize: function(aSize) {
+ let icons = this._localeProp("icons");
+ if (!icons)
+ return null;
+ let dist = 100000;
+ let icon = null;
+ for (let size in icons) {
+ let iSize = parseInt(size);
+ if (Math.abs(iSize - aSize) < dist) {
+ icon = this._origin.resolve(icons[size]);
+ dist = Math.abs(iSize - aSize);
+ }
+ }
+ return icon;
+ },
+
+ fullLaunchPath: function() {
+ let launchPath = this._localeProp("launch_path");
+ return this._origin.resolve(launchPath ? launchPath : "");
+ }
+}
+
+DOMApplicationRegistry.init();
new file mode 100644
--- /dev/null
+++ b/dom/base/Webapps.manifest
@@ -0,0 +1,10 @@
+# Webapps.js
+component {fff440b3-fae2-45c1-bf03-3b5a2e432270} Webapps.js
+contract @mozilla.org/webapps;1 {fff440b3-fae2-45c1-bf03-3b5a2e432270}
+category JavaScript-navigator-property mozApps @mozilla.org/webapps;1
+
+component {723ed303-7757-4fb0-b261-4f78b1f6bd22} Webapps.js
+contract @mozilla.org/webapps/application;1 {723ed303-7757-4fb0-b261-4f78b1f6bd22}
+
+component {b4937718-11a3-400b-a69f-ab442a418569} Webapps.js
+contract @mozilla.org/webapps/error;1 {b4937718-11a3-400b-a69f-ab442a418569}
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/apps/Makefile.in
@@ -0,0 +1,53 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is web apps.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Andreas Gal <gal@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+
+DEPTH = ../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = dom
+XPIDL_MODULE = dom_apps
+GRE_MODULE = 1
+
+XPIDLSRCS = \
+ nsIDOMApplicationRegistry.idl \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl
@@ -0,0 +1,89 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is web apps.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Andreas Gal <gal@mozilla.com> (Original Author)
+ * Fabrice Desré <fabrice@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "domstubs.idl"
+
+[scriptable, uuid(e0c271cb-266b-48c9-a7e4-96590b445c26)]
+interface mozIDOMApplicationRegistryError : nsISupports
+{
+ const unsigned short DENIED = 1;
+ const unsigned short PERMISSION_DENIED = 2;
+ const unsigned short MANIFEST_URL_ERROR = 3;
+ const unsigned short NETWORK_ERROR = 4;
+ const unsigned short MANIFEST_PARSE_ERROR = 5;
+ const unsigned short INVALID_MANIFEST = 6;
+
+ readonly attribute short code;
+};
+
+[scriptable, uuid(a6856a3d-dece-43ce-89b9-72dba07f4246)]
+interface mozIDOMApplication : nsISupports
+{
+ readonly attribute jsval manifest;
+ readonly attribute DOMString receipt;
+ readonly attribute DOMString origin;
+ readonly attribute DOMString installOrigin;
+ readonly attribute unsigned long installTime;
+};
+
+[scriptable, function, uuid(be170df5-9154-463b-9197-10a6195eba52)]
+interface mozIDOMApplicationRegistryEnumerateCallback : nsISupports
+{
+ void handleEvent([array, size_is(count)] in mozIDOMApplication apps,
+ in unsigned long count);
+};
+
+[scriptable, function, uuid(ae0ed33d-35cf-443a-837b-a6cebf16bd49)]
+interface mozIDOMApplicationRegistryErrorCallback : nsISupports
+{
+ void handleEvent(in mozIDOMApplicationRegistryError error);
+};
+
+[scriptable, uuid(4070ea6f-dca1-4052-8bc6-7a9bcfc314ac)]
+interface mozIDOMApplicationRegistry : nsISupports
+{
+ void install(in DOMString manifestUrl,
+ [optional] in DOMString receipt);
+ void uninstall(in DOMString origin);
+ void enumerate(in mozIDOMApplicationRegistryEnumerateCallback success,
+ [optional] in mozIDOMApplicationRegistryErrorCallback error);
+ void launch(in DOMString origin);
+
+ attribute nsIDOMEventListener oninstall;
+ attribute nsIDOMEventListener onuninstall;
+ attribute nsIDOMEventListener onerror;
+};
--- a/dom/interfaces/base/Makefile.in
+++ b/dom/interfaces/base/Makefile.in
@@ -52,17 +52,17 @@ SDK_XPIDLSRCS =
nsIDOMWindow.idl \
nsIDOMWindowCollection.idl \
nsIDOMWindowUtils.idl \
$(NULL)
XPIDLSRCS = \
nsIFrameRequestCallback.idl \
nsIBrowserDOMWindow.idl \
- nsIContentPermissionPrompt.idl \
+ nsIContentPermissionPrompt.idl \
nsIContentPrefService.idl \
nsIContentURIGrouper.idl \
nsIDOMClientInformation.idl \
nsIDOMConstructor.idl \
nsIDOMCRMFObject.idl \
nsIDOMCrypto.idl \
nsIDOMHistory.idl \
nsIDOMLocation.idl \
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -892,72 +892,29 @@ TabChild::InitTabChildGlobal()
return true;
nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mWebNav);
NS_ENSURE_TRUE(window, false);
nsCOMPtr<nsIDOMEventTarget> chromeHandler =
do_QueryInterface(window->GetChromeEventHandler());
NS_ENSURE_TRUE(chromeHandler, false);
- nsCOMPtr<nsIJSRuntimeService> runtimeSvc =
- do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
- NS_ENSURE_TRUE(runtimeSvc, false);
-
- JSRuntime* rt = nsnull;
- runtimeSvc->GetRuntime(&rt);
- NS_ENSURE_TRUE(rt, false);
-
- JSContext* cx = JS_NewContext(rt, 8192);
- NS_ENSURE_TRUE(cx, false);
-
- mCx = cx;
-
- nsContentUtils::XPConnect()->SetSecurityManagerForJSContext(cx, nsContentUtils::GetSecurityManager(), 0);
- nsContentUtils::GetSecurityManager()->GetSystemPrincipal(getter_AddRefs(mPrincipal));
-
- JS_SetNativeStackQuota(cx, 128 * sizeof(size_t) * 1024);
-
- JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_PRIVATE_IS_NSISUPPORTS);
- JS_SetVersion(cx, JSVERSION_LATEST);
- JS_SetErrorReporter(cx, ContentScriptErrorReporter);
-
- xpc_LocalizeContext(cx);
-
- JSAutoRequest ar(cx);
- nsIXPConnect* xpc = nsContentUtils::XPConnect();
- const PRUint32 flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES |
- /*nsIXPConnect::OMIT_COMPONENTS_OBJECT ? |*/
- nsIXPConnect::FLAG_SYSTEM_GLOBAL_OBJECT;
-
nsRefPtr<TabChildGlobal> scope = new TabChildGlobal(this);
NS_ENSURE_TRUE(scope, false);
mTabChildGlobal = scope;
nsISupports* scopeSupports =
NS_ISUPPORTS_CAST(nsIDOMEventTarget*, scope);
- JS_SetContextPrivate(cx, scopeSupports);
-
- nsresult rv =
- xpc->InitClassesWithNewWrappedGlobal(cx, scopeSupports,
- NS_GET_IID(nsISupports),
- scope->GetPrincipal(), nsnull,
- flags, getter_AddRefs(mGlobal));
- NS_ENSURE_SUCCESS(rv, false);
+
+ NS_ENSURE_TRUE(InitTabChildGlobalInternal(scopeSupports), false);
nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(chromeHandler);
NS_ENSURE_TRUE(root, false);
root->SetParentTarget(scope);
-
- JSObject* global = nsnull;
- rv = mGlobal->GetJSObject(&global);
- NS_ENSURE_SUCCESS(rv, false);
-
- JS_SetGlobalObject(cx, global);
- DidCreateCx();
return true;
}
bool
TabChild::InitWidget(const nsIntSize& size)
{
NS_ABORT_IF_FALSE(!mWidget && !mRemoteFrame, "CreateWidget twice?");
--- a/dom/plugins/base/android/ANPSurface.cpp
+++ b/dom/plugins/base/android/ANPSurface.cpp
@@ -42,17 +42,16 @@
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args)
#define ASSIGN(obj, name) (obj)->name = anp_surface_##name
#define CLEAR_EXCEPTION(env) if (env->ExceptionOccurred()) env->ExceptionClear();
#define ANDROID_REGION_SIZE 512
-// Copied from Android headers
enum {
PIXEL_FORMAT_RGBA_8888 = 1,
PIXEL_FORMAT_RGB_565 = 4,
};
struct SurfaceInfo {
uint32_t w;
uint32_t h;
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -585,23 +585,26 @@ nsEditorEventListener::MouseClick(nsIDOM
// Would've used the alt key, but the kde wmgr treats alt-middle specially.
bool ctrlKey = false;
mouseEvent->GetCtrlKey(&ctrlKey);
nsCOMPtr<nsIEditorMailSupport> mailEditor;
if (ctrlKey)
mailEditor = do_QueryObject(mEditor);
- PRInt32 clipboard;
-
-#if defined(XP_OS2) || defined(XP_WIN32)
- clipboard = nsIClipboard::kGlobalClipboard;
-#else
- clipboard = nsIClipboard::kSelectionClipboard;
-#endif
+ PRInt32 clipboard = nsIClipboard::kGlobalClipboard;
+ nsCOMPtr<nsIClipboard> clipboardService =
+ do_GetService("@mozilla.org/widget/clipboard;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool selectionSupported;
+ rv = clipboardService->SupportsSelectionClipboard(&selectionSupported);
+ if (NS_SUCCEEDED(rv) && selectionSupported) {
+ clipboard = nsIClipboard::kSelectionClipboard;
+ }
+ }
if (mailEditor)
mailEditor->PasteAsQuotation(clipboard);
else
mEditor->Paste(clipboard);
// Prevent the event from propagating up to be possibly handled
// again by the containing window:
--- a/editor/libeditor/html/tests/Makefile.in
+++ b/editor/libeditor/html/tests/Makefile.in
@@ -82,33 +82,29 @@ include $(topsrcdir)/config/rules.mk
test_bug612447.html \
test_bug620906.html \
test_bug622371.html \
test_bug629845.html \
test_bug640321.html \
test_bug668599.html \
test_bug674770-1.html \
file_bug674770-1.html \
+ test_bug674770-2.html \
test_bug674861.html \
test_bug676401.html \
test_bug677752.html \
test_bug697842.html \
test_CF_HTML_clipboard.html \
test_contenteditable_focus.html \
test_htmleditor_keyevent_handling.html \
test_select_all_without_body.html \
file_select_all_without_body.html \
test_root_element_replacement.html \
$(NULL)
-ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
-_TEST_FILES += test_bug674770-2.html \
- $(NULL)
-endif
-
ifneq (mobile,$(MOZ_BUILD_APP))
_TEST_FILES += test_spellcheck_pref.html \
$(NULL)
endif
_DATA_FILES = \
data/cfhtml-chromium.txt \
data/cfhtml-firefox.txt \
--- a/editor/libeditor/html/tests/test_bug674770-2.html
+++ b/editor/libeditor/html/tests/test_bug674770-2.html
@@ -59,19 +59,19 @@ SimpleTest.waitForFocus(function() {
frameDocument.getElementById("editor2").addEventListener("click", clickEventHnalder, false);
var text = frameDocument.getElementById("text");
text.focus();
SimpleTest.executeSoon(function() {
synthesizeKey("a", { accelKey: true }, frameWindow);
- // Windows doesn't have primary selection, we should copy the text to the
- // global clipboard.
- if (navigator.platform.indexOf("Win") == 0) {
+ // Windows and Mac don't have primary selection, we should copy the text to
+ // the global clipboard.
+ if (!SpecialPowers.supportsSelectionClipboard()) {
SimpleTest.waitForClipboard("pasted",
function() { synthesizeKey("c", { accelKey: true }, frameWindow); },
function() { SimpleTest.executeSoon(runInputTests1) },
cleanup);
} else {
// Otherwise, don't call waitForClipboard since it breaks primary
// selection.
runInputTests1();
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -1308,17 +1308,17 @@ public:
* Context reset constants.
* These are used to determine who is guilty when a context reset
* happens.
*/
enum ContextResetARB {
CONTEXT_NO_ERROR = 0,
CONTEXT_GUILTY_CONTEXT_RESET_ARB = 0x8253,
CONTEXT_INNOCENT_CONTEXT_RESET_ARB = 0x8254,
- CONTEXT_UNKNOWN_CONTEXT_RESET_ARB = 0x8255,
+ CONTEXT_UNKNOWN_CONTEXT_RESET_ARB = 0x8255
};
bool HasRobustness() {
return mHasRobustness;
}
protected:
bool mInitialized;
--- a/gfx/src/nsRegion.cpp
+++ b/gfx/src/nsRegion.cpp
@@ -210,37 +210,62 @@ void RgnRectMemoryAllocator::Free (nsReg
mFreeEntries++;
aRect->next = mFreeListHead;
mFreeListHead = aRect;
Unlock ();
}
// Global pool for nsRegion::RgnRect allocation
-static RgnRectMemoryAllocator* gRectPool;
+static PRUintn gRectPoolTlsIndex;
+
+void RgnRectMemoryAllocatorDTOR(void *priv)
+{
+ RgnRectMemoryAllocator* allocator = (static_cast<RgnRectMemoryAllocator*>(
+ PR_GetThreadPrivate(gRectPoolTlsIndex)));
+ delete allocator;
+}
nsresult nsRegion::InitStatic()
{
- gRectPool = new RgnRectMemoryAllocator(INIT_MEM_CHUNK_ENTRIES);
- return !gRectPool ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
+ return PR_NewThreadPrivateIndex(&gRectPoolTlsIndex, RgnRectMemoryAllocatorDTOR);
}
void nsRegion::ShutdownStatic()
{
- delete gRectPool;
+ RgnRectMemoryAllocator* allocator = (static_cast<RgnRectMemoryAllocator*>(
+ PR_GetThreadPrivate(gRectPoolTlsIndex)));
+ if (!allocator)
+ return;
+
+ delete allocator;
+
+ PR_SetThreadPrivate(gRectPoolTlsIndex, nsnull);
}
void* nsRegion::RgnRect::operator new (size_t) CPP_THROW_NEW
{
- return gRectPool->Alloc ();
+ RgnRectMemoryAllocator* allocator = (static_cast<RgnRectMemoryAllocator*>(
+ PR_GetThreadPrivate(gRectPoolTlsIndex)));
+ if (!allocator) {
+ allocator = new RgnRectMemoryAllocator(INIT_MEM_CHUNK_ENTRIES);
+ PR_SetThreadPrivate(gRectPoolTlsIndex, allocator);
+ }
+ return allocator->Alloc ();
}
void nsRegion::RgnRect::operator delete (void* aRect, size_t)
{
- gRectPool->Free (static_cast<RgnRect*>(aRect));
+ RgnRectMemoryAllocator* allocator = (static_cast<RgnRectMemoryAllocator*>(
+ PR_GetThreadPrivate(gRectPoolTlsIndex)));
+ if (!allocator) {
+ NS_ERROR("Invalid nsRegion::RgnRect delete");
+ return;
+ }
+ allocator->Free (static_cast<RgnRect*>(aRect));
}
void nsRegion::Init()
{
mRectListHead.prev = mRectListHead.next = &mRectListHead;
mCurRect = &mRectListHead;
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -73,16 +73,17 @@
#include "gfxTextRunWordCache.h"
#include "nsUnicharUtils.h"
#include "nsUnicodeRange.h"
#include "gfxUnicodeProperties.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
using namespace mozilla;
// font info loader constants
static const PRUint32 kDelayBeforeLoadingCmaps = 8 * 1000; // 8secs
static const PRUint32 kIntervalBetweenLoadingCmaps = 150; // 150ms
static const PRUint32 kNumFontsPerSlice = 10; // read in info 10 fonts at a time
@@ -401,47 +402,61 @@ gfxPlatformFontList::FindFontForChar(con
gfxFontStyle normalStyle;
fontEntry = FindFontForFamily(mReplacementCharFallbackFamily, &normalStyle, needsBold);
}
if (fontEntry && fontEntry->TestCharacterMap(aCh))
return fontEntry;
}
+ static bool first = true;
+ TimeStamp start = TimeStamp::Now();
+
FontSearch data(aCh, aPrevFont);
// iterate over all font families to find a font that support the character
mFontFamilies.Enumerate(gfxPlatformFontList::FindFontForCharProc, &data);
+ TimeDuration elapsed = TimeStamp::Now() - start;
+
#ifdef PR_LOGGING
PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
if (NS_UNLIKELY(log)) {
PRUint32 charRange = gfxFontUtils::CharRangeBit(aCh);
PRUint32 unicodeRange = FindCharUnicodeRange(aCh);
PRUint32 hbscript = gfxUnicodeProperties::GetScriptCode(aCh);
PR_LOG(log, PR_LOG_DEBUG,\
("(textrun-systemfallback) char: u+%6.6x "
- "char-range: %d unicode-range: %d script: %d match: [%s] count: %d\n",
+ "char-range: %d unicode-range: %d script: %d match: [%s]"
+ " count: %d time: %dus\n",
aCh,
charRange, unicodeRange, hbscript,
(data.mBestMatch ?
NS_ConvertUTF16toUTF8(data.mBestMatch->Name()).get() :
"<none>"),
- data.mCount));
+ data.mCount,
+ PRInt32(elapsed.ToMicroseconds())));
}
#endif
// no match? add to set of non-matching codepoints
if (!data.mBestMatch) {
mCodepointsWithNoFonts.set(aCh);
} else if (aCh == 0xFFFD) {
mReplacementCharFallbackFamily = data.mBestMatch->FamilyName();
}
+ PRInt32 intElapsed = PRInt32(first ? elapsed.ToMilliseconds() :
+ elapsed.ToMicroseconds());
+ Telemetry::Accumulate((first ? Telemetry::SYSTEM_FONT_FALLBACK_FIRST :
+ Telemetry::SYSTEM_FONT_FALLBACK),
+ intElapsed);
+ first = false;
+
return data.mBestMatch;
}
PLDHashOperator PR_CALLBACK
gfxPlatformFontList::FindFontForCharProc(nsStringHashKey::KeyType aKey, nsRefPtr<gfxFontFamily>& aFamilyEntry,
void *userArg)
{
FontSearch *data = static_cast<FontSearch*>(userArg);
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -512,18 +512,23 @@ ExecuteRegExp(JSContext *cx, Native nati
bool ok;
JSObject *obj = NonGenericMethodGuard(cx, args, native, &RegExpClass, &ok);
if (!obj)
return ok;
RegExpObject *reobj = obj->asRegExp();
RegExpMatcher matcher(cx);
- if (!matcher.reset(reobj))
- return false;
+ if (reobj->startsWithAtomizedGreedyStar()) {
+ if (!matcher.resetWithTestOptimized(reobj))
+ return false;
+ } else {
+ if (!matcher.reset(reobj))
+ return false;
+ }
RegExpStatics *res = cx->regExpStatics();
/* Step 2. */
JSString *input = js_ValueToString(cx, (args.length() > 0) ? args[0] : UndefinedValue());
if (!input)
return false;
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -688,16 +688,20 @@ InitTypeConstructor(JSContext* cx,
// to all CData objects created from this type constructor. (These will
// become functions and properties on CData objects created from this type.)
if (instanceFns && !JS_DefineFunctions(cx, dataProto, instanceFns))
return false;
if (instanceProps && !JS_DefineProperties(cx, dataProto, instanceProps))
return false;
+ // Link the type prototype to the data prototype.
+ if (!JS_SetReservedSlot(cx, typeProto, SLOT_OURDATAPROTO, OBJECT_TO_JSVAL(dataProto)))
+ return false;
+
if (!JS_FreezeObject(cx, obj) ||
//!JS_FreezeObject(cx, dataProto) || // XXX fixme - see bug 541212!
!JS_FreezeObject(cx, typeProto))
return false;
return true;
}
@@ -735,17 +739,17 @@ InitInt64Class(JSContext* cx,
return prototype;
}
static JSBool
AttachProtos(JSContext* cx, JSObject* proto, JSObject** protos)
{
// For a given 'proto' of [[Class]] "CTypeProto", attach each of the 'protos'
// to the appropriate CTypeProtoSlot. (SLOT_UINT64PROTO is the last slot
- // of [[Class]] "CTypeProto".)
+ // of [[Class]] "CTypeProto" that we fill in this automated manner.)
for (JSUint32 i = 0; i <= SLOT_UINT64PROTO; ++i) {
if (!JS_SetReservedSlot(cx, proto, i, OBJECT_TO_JSVAL(protos[i])))
return false;
}
return true;
}
@@ -779,16 +783,21 @@ InitTypeClasses(JSContext* cx, JSObject*
// * 'prototype' property:
// * [[Class]] "CDataProto"
// * 'constructor' property === ctypes.CData
// * Provides properties and functions common to all CDatas.
JSObject* CDataProto = InitCDataClass(cx, parent, CTypeProto);
if (!CDataProto)
return false;
+ // Link CTypeProto to CDataProto.
+ if (!JS_SetReservedSlot(cx, CTypeProto, SLOT_OURDATAPROTO,
+ OBJECT_TO_JSVAL(CDataProto)))
+ return false;
+
// Create and attach the special class constructors: ctypes.PointerType,
// ctypes.ArrayType, ctypes.StructType, and ctypes.FunctionType.
// Each of these constructors 'c' has, respectively:
// * [[Class]] "Function"
// * __proto__ === Function.prototype
// * A constructor that creates a user-defined type.
// * 'prototype' property:
// * [[Class]] "CTypeProto"
@@ -2824,16 +2833,22 @@ CType::Trace(JSTracer* trc, JSObject* ob
}
bool
CType::IsCType(JSContext* cx, JSObject* obj)
{
return JS_GET_CLASS(cx, obj) == &sCTypeClass;
}
+bool
+CType::IsCTypeProto(JSContext* cx, JSObject* obj)
+{
+ return JS_GET_CLASS(cx, obj) == &sCTypeProtoClass;
+}
+
TypeCode
CType::GetTypeCode(JSContext* cx, JSObject* typeObj)
{
JS_ASSERT(IsCType(cx, typeObj));
jsval result;
ASSERT_OK(JS_GetReservedSlot(cx, typeObj, SLOT_TYPECODE, &result));
return TypeCode(JSVAL_TO_INT(result));
@@ -3029,49 +3044,51 @@ JSObject*
CType::GetProtoFromCtor(JSContext* cx, JSObject* obj, CTypeProtoSlot slot)
{
// Get ctypes.{Pointer,Array,Struct}Type.prototype from a reserved slot
// on the type constructor.
jsval protoslot;
ASSERT_OK(JS_GetReservedSlot(cx, obj, SLOT_FN_CTORPROTO, &protoslot));
JSObject* proto = JSVAL_TO_OBJECT(protoslot);
JS_ASSERT(proto);
- JS_ASSERT(JS_GET_CLASS(cx, proto) == &sCTypeProtoClass);
+ JS_ASSERT(CType::IsCTypeProto(cx, proto));
// Get the desired prototype.
jsval result;
ASSERT_OK(JS_GetReservedSlot(cx, proto, slot, &result));
return JSVAL_TO_OBJECT(result);
}
JSObject*
CType::GetProtoFromType(JSContext* cx, JSObject* obj, CTypeProtoSlot slot)
{
JS_ASSERT(IsCType(cx, obj));
// Get the prototype of the type object.
JSObject* proto = JS_GetPrototype(cx, obj);
JS_ASSERT(proto);
- JS_ASSERT(JS_GET_CLASS(cx, proto) == &sCTypeProtoClass);
+ JS_ASSERT(CType::IsCTypeProto(cx, proto));
// Get the requested ctypes.{Pointer,Array,Struct,Function}Type.prototype.
jsval result;
ASSERT_OK(JS_GetReservedSlot(cx, proto, slot, &result));
return JSVAL_TO_OBJECT(result);
}
JSBool
CType::PrototypeGetter(JSContext* cx, JSObject* obj, jsid idval, jsval* vp)
{
- if (!CType::IsCType(cx, obj)) {
- JS_ReportError(cx, "not a CType");
+ if (!(CType::IsCType(cx, obj) || CType::IsCTypeProto(cx, obj))) {
+ JS_ReportError(cx, "not a CType or CTypeProto");
return JS_FALSE;
}
- ASSERT_OK(JS_GetReservedSlot(cx, obj, SLOT_PROTO, vp));
+ unsigned slot = CType::IsCTypeProto(cx, obj) ? (unsigned) SLOT_OURDATAPROTO
+ : (unsigned) SLOT_PROTO;
+ ASSERT_OK(JS_GetReservedSlot(cx, obj, slot, vp));
JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp) || JSVAL_IS_VOID(*vp));
return JS_TRUE;
}
JSBool
CType::NameGetter(JSContext* cx, JSObject* obj, jsid idval, jsval* vp)
{
if (!CType::IsCType(cx, obj)) {
@@ -3146,62 +3163,80 @@ CType::CreateArray(JSContext* cx, uintN
JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(result));
return JS_TRUE;
}
JSBool
CType::ToString(JSContext* cx, uintN argc, jsval* vp)
{
JSObject* obj = JS_THIS_OBJECT(cx, vp);
- if (!obj || !CType::IsCType(cx, obj)) {
+ if (!obj ||
+ !(CType::IsCType(cx, obj) || CType::IsCTypeProto(cx, obj)))
+ {
JS_ReportError(cx, "not a CType");
return JS_FALSE;
}
- AutoString type;
- AppendString(type, "type ");
- AppendString(type, GetName(cx, obj));
-
- JSString* result = NewUCString(cx, type);
+ // Create the appropriate string depending on whether we're sCTypeClass or
+ // sCTypeProtoClass.
+ JSString* result;
+ if (CType::IsCType(cx, obj)) {
+ AutoString type;
+ AppendString(type, "type ");
+ AppendString(type, GetName(cx, obj));
+ result = NewUCString(cx, type);
+ }
+ else {
+ result = JS_NewStringCopyZ(cx, "[CType proto object]");
+ }
if (!result)
return JS_FALSE;
-
+
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result));
return JS_TRUE;
}
JSBool
CType::ToSource(JSContext* cx, uintN argc, jsval* vp)
{
JSObject* obj = JS_THIS_OBJECT(cx, vp);
- if (!obj || !CType::IsCType(cx, obj)) {
+ if (!obj ||
+ !(CType::IsCType(cx, obj) || CType::IsCTypeProto(cx, obj)))
+ {
JS_ReportError(cx, "not a CType");
return JS_FALSE;
}
- AutoString source;
- BuildTypeSource(cx, obj, false, source);
- JSString* result = NewUCString(cx, source);
+ // Create the appropriate string depending on whether we're sCTypeClass or
+ // sCTypeProtoClass.
+ JSString* result;
+ if (CType::IsCType(cx, obj)) {
+ AutoString source;
+ BuildTypeSource(cx, obj, false, source);
+ result = NewUCString(cx, source);
+ } else {
+ result = JS_NewStringCopyZ(cx, "[CType proto object]");
+ }
if (!result)
return JS_FALSE;
-
+
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result));
return JS_TRUE;
}
JSBool
CType::HasInstance(JSContext* cx, JSObject* obj, const jsval* v, JSBool* bp)
{
JS_ASSERT(CType::IsCType(cx, obj));
jsval slot;
ASSERT_OK(JS_GetReservedSlot(cx, obj, SLOT_PROTO, &slot));
JSObject* prototype = JSVAL_TO_OBJECT(slot);
JS_ASSERT(prototype);
- JS_ASSERT(JS_GET_CLASS(cx, prototype) == &sCDataProtoClass);
+ JS_ASSERT(CData::IsCDataProto(cx, prototype));
*bp = JS_FALSE;
if (JSVAL_IS_PRIMITIVE(*v))
return JS_TRUE;
JSObject* proto = JSVAL_TO_OBJECT(*v);
while ((proto = JS_GetPrototype(cx, proto))) {
if (proto == prototype) {
@@ -5264,17 +5299,17 @@ CClosure::Create(JSContext* cx,
JS_ReportOutOfMemory(cx);
return NULL;
}
// Get the prototype of the FunctionType object, of class CTypeProto,
// which stores our JSContext for use with the closure.
JSObject* proto = JS_GetPrototype(cx, typeObj);
JS_ASSERT(proto);
- JS_ASSERT(JS_GET_CLASS(cx, proto) == &sCTypeProtoClass);
+ JS_ASSERT(CType::IsCTypeProto(cx, proto));
// Get a JSContext for use with the closure.
jsval slot;
ASSERT_OK(JS_GetReservedSlot(cx, proto, SLOT_CLOSURECX, &slot));
if (!JSVAL_IS_VOID(slot)) {
// Use the existing JSContext.
cinfo->cx = static_cast<JSContext*>(JSVAL_TO_PRIVATE(slot));
JS_ASSERT(cinfo->cx);
@@ -5694,16 +5729,22 @@ CData::GetData(JSContext* cx, JSObject*
}
bool
CData::IsCData(JSContext* cx, JSObject* obj)
{
return JS_GET_CLASS(cx, obj) == &sCDataClass;
}
+bool
+CData::IsCDataProto(JSContext* cx, JSObject* obj)
+{
+ return JS_GET_CLASS(cx, obj) == &sCDataProtoClass;
+}
+
JSBool
CData::ValueGetter(JSContext* cx, JSObject* obj, jsid idval, jsval* vp)
{
if (!IsCData(cx, obj)) {
JS_ReportError(cx, "not a CData");
return JS_FALSE;
}
@@ -5928,39 +5969,45 @@ JSBool
CData::ToSource(JSContext* cx, uintN argc, jsval* vp)
{
if (argc != 0) {
JS_ReportError(cx, "toSource takes zero arguments");
return JS_FALSE;
}
JSObject* obj = JS_THIS_OBJECT(cx, vp);
- if (!obj || !CData::IsCData(cx, obj)) {
+ if (!obj ||
+ !(CData::IsCData(cx, obj) || CData::IsCDataProto(cx, obj))) {
JS_ReportError(cx, "not a CData");
return JS_FALSE;
}
- JSObject* typeObj = CData::GetCType(cx, obj);
- void* data = CData::GetData(cx, obj);
-
- // Walk the types, building up the toSource() string.
- // First, we build up the type expression:
- // 't.ptr' for pointers;
- // 't.array([n])' for arrays;
- // 'n' for structs, where n = t.name, the struct's name. (We assume this is
- // bound to a variable in the current scope.)
- AutoString source;
- BuildTypeSource(cx, typeObj, true, source);
- AppendString(source, "(");
- if (!BuildDataSource(cx, typeObj, data, false, source))
- return JS_FALSE;
-
- AppendString(source, ")");
-
- JSString* result = NewUCString(cx, source);
+ JSString* result;
+ if (CData::IsCData(cx, obj)) {
+ JSObject* typeObj = CData::GetCType(cx, obj);
+ void* data = CData::GetData(cx, obj);
+
+ // Walk the types, building up the toSource() string.
+ // First, we build up the type expression:
+ // 't.ptr' for pointers;
+ // 't.array([n])' for arrays;
+ // 'n' for structs, where n = t.name, the struct's name. (We assume this is
+ // bound to a variable in the current scope.)
+ AutoString source;
+ BuildTypeSource(cx, typeObj, true, source);
+ AppendString(source, "(");
+ if (!BuildDataSource(cx, typeObj, data, false, source))
+ return JS_FALSE;
+
+ AppendString(source, ")");
+
+ result = NewUCString(cx, source);
+ }
+ else
+ result = JS_NewStringCopyZ(cx, "[CData proto object]");
if (!result)
return JS_FALSE;
JS_SET_RVAL(cx, vp, STRING_TO_JSVAL(result));
return JS_TRUE;
}
/*******************************************************************************
--- a/js/src/ctypes/CTypes.h
+++ b/js/src/ctypes/CTypes.h
@@ -387,17 +387,18 @@ enum CTypeProtoSlot {
SLOT_FUNCTIONPROTO = 3, // ctypes.FunctionType.prototype object
SLOT_CDATAPROTO = 4, // ctypes.CData.prototype object
SLOT_POINTERDATAPROTO = 5, // common ancestor of all CData objects of PointerType
SLOT_ARRAYDATAPROTO = 6, // common ancestor of all CData objects of ArrayType
SLOT_STRUCTDATAPROTO = 7, // common ancestor of all CData objects of StructType
SLOT_FUNCTIONDATAPROTO = 8, // common ancestor of all CData objects of FunctionType
SLOT_INT64PROTO = 9, // ctypes.Int64.prototype object
SLOT_UINT64PROTO = 10, // ctypes.UInt64.prototype object
- SLOT_CLOSURECX = 11, // JSContext for use with FunctionType closures
+ SLOT_OURDATAPROTO = 11, // the data prototype corresponding to this object
+ SLOT_CLOSURECX = 12, // JSContext for use with FunctionType closures
CTYPEPROTO_SLOTS
};
enum CTypeSlot {
SLOT_PROTO = 0, // 'prototype' property of the CType object
SLOT_TYPECODE = 1, // TypeCode of the CType object
SLOT_FFITYPE = 2, // ffi_type representing the type
SLOT_NAME = 3, // name of the type
@@ -452,16 +453,17 @@ namespace CType {
JSObject* Create(JSContext* cx, JSObject* typeProto, JSObject* dataProto,
TypeCode type, JSString* name, jsval size, jsval align, ffi_type* ffiType);
JSObject* DefineBuiltin(JSContext* cx, JSObject* parent, const char* propName,
JSObject* typeProto, JSObject* dataProto, const char* name, TypeCode type,
jsval size, jsval align, ffi_type* ffiType);
bool IsCType(JSContext* cx, JSObject* obj);
+ bool IsCTypeProto(JSContext* cx, JSObject* obj);
TypeCode GetTypeCode(JSContext* cx, JSObject* typeObj);
bool TypesEqual(JSContext* cx, JSObject* t1, JSObject* t2);
size_t GetSize(JSContext* cx, JSObject* obj);
bool GetSafeSize(JSContext* cx, JSObject* obj, size_t* result);
bool IsSizeDefined(JSContext* cx, JSObject* obj);
size_t GetAlignment(JSContext* cx, JSObject* obj);
ffi_type* GetFFIType(JSContext* cx, JSObject* obj);
JSString* GetName(JSContext* cx, JSObject* obj);
@@ -515,16 +517,17 @@ namespace CClosure {
namespace CData {
JSObject* Create(JSContext* cx, JSObject* typeObj, JSObject* refObj,
void* data, bool ownResult);
JSObject* GetCType(JSContext* cx, JSObject* dataObj);
void* GetData(JSContext* cx, JSObject* dataObj);
bool IsCData(JSContext* cx, JSObject* obj);
+ bool IsCDataProto(JSContext* cx, JSObject* obj);
// Attached by JSAPI as the function 'ctypes.cast'
JSBool Cast(JSContext* cx, uintN argc, jsval* vp);
// Attached by JSAPI as the function 'ctypes.getRuntime'
JSBool GetRuntime(JSContext* cx, uintN argc, jsval* vp);
}
namespace Int64 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug691299-regexp.js
@@ -0,0 +1,3 @@
+// |jit-test| error: SyntaxError: invalid quantifier
+
+String.fromCharCode(256).replace(/[^a$]{4294967295}/,"aaaa");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug691797-regexp-1.js
@@ -0,0 +1,4 @@
+var re = /.*star.*/i;
+var str = "The Shawshank Redemption (1994)";
+for (var k = 0; k < 100; k++)
+ assertEq(false, re.test(str));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug691797-regexp-2.js
@@ -0,0 +1,6 @@
+var re = /.*(a\w).*/i;
+var str = "abccccccad";
+for (var k = 0; k < 100; k++) {
+ re.test(str);
+ assertEq('ad', RegExp['$1']);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/jaeger/bug706110.js
@@ -0,0 +1,18 @@
+function test() {
+ function Bug() { this.prototype }
+ var actual = (new Bug instanceof Bug);
+ assertEq(actual, true);
+}
+test();
+test();
+
+function testLambdaCtor() {
+ var q;
+ for (var x = 0; x < 2; ++x) {
+ var f = function(){};
+ if (x == 1) gc();
+ q = new f;
+ }
+ return q.__proto__ === f.prototype;
+}
+assertEq(testLambdaCtor(), true);
--- a/js/src/jsprvtd.h
+++ b/js/src/jsprvtd.h
@@ -125,16 +125,17 @@ class RegExpMatcher;
class RegExpObjectBuilder;
class RegExpStatics;
class MatchPairs;
namespace detail {
class RegExpPrivate;
class RegExpPrivateCode;
+class RegExpPrivateCacheValue;
} /* namespace detail */
enum RegExpFlag
{
IgnoreCaseFlag = 0x01,
GlobalFlag = 0x02,
MultilineFlag = 0x04,
@@ -231,17 +232,20 @@ typedef Vector<UpvarCookie, 8> UpvarCook
class Breakpoint;
class BreakpointSite;
typedef HashMap<jsbytecode *, BreakpointSite *, DefaultHasher<jsbytecode *>, RuntimeAllocPolicy>
BreakpointSiteMap;
class Debugger;
class WatchpointMap;
-typedef HashMap<JSAtom *, detail::RegExpPrivate *, DefaultHasher<JSAtom *>, RuntimeAllocPolicy>
+typedef HashMap<JSAtom *,
+ detail::RegExpPrivateCacheValue,
+ DefaultHasher<JSAtom *>,
+ RuntimeAllocPolicy>
RegExpPrivateCache;
typedef JSNative Native;
typedef JSPropertyOp PropertyOp;
typedef JSStrictPropertyOp StrictPropertyOp;
typedef JSPropertyDescriptor PropertyDescriptor;
namespace analyze {
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -7267,18 +7267,22 @@ mjit::Compiler::leaveBlock()
bool
mjit::Compiler::constructThis()
{
JS_ASSERT(isConstructing);
JSFunction *fun = script->function();
do {
- if (!cx->typeInferenceEnabled() || fun->getType(cx)->unknownProperties())
+ if (!cx->typeInferenceEnabled() ||
+ !fun->hasSingletonType() ||
+ fun->getType(cx)->unknownProperties())
+ {
break;
+ }
jsid id = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
types::TypeSet *protoTypes = fun->getType(cx)->getProperty(cx, id, false);
JSObject *proto = protoTypes->getSingleton(cx, true);
if (!proto)
break;
--- a/js/src/tests/js1_5/GC/jstests.list
+++ b/js/src/tests/js1_5/GC/jstests.list
@@ -1,11 +1,11 @@
url-prefix ../../jsreftest.html?test=js1_5/GC/
script regress-104584.js
-skip-if(!xulRuntime.shell&&xulRuntime.OS=="Darwin") slow script regress-203278-2.js
+script regress-203278-2.js
script regress-203278-3.js
script regress-278725.js
script regress-306788.js
script regress-311497.js
script regress-313276.js
script regress-313479.js
script regress-316885-01.js
script regress-316885-02.js
--- a/js/src/tests/js1_5/Regress/jstests.list
+++ b/js/src/tests/js1_5/Regress/jstests.list
@@ -36,17 +36,17 @@ skip script regress-173067.js # obsolete
script regress-174709.js
script regress-176125.js
script regress-179524.js
script regress-185165.js
script regress-191633.js
script regress-191668.js
script regress-192414.js
script regress-193418.js
-skip script regress-203278-1.js # slow
+script regress-203278-1.js
script regress-203402.js
script regress-203841.js
script regress-204210.js
script regress-210682.js
script regress-211590.js
script regress-213482.js
script regress-214761.js
script regress-216320.js
--- a/js/src/tests/js1_8/regress/jstests.list
+++ b/js/src/tests/js1_8/regress/jstests.list
@@ -59,17 +59,17 @@ script regress-467495-02.js
script regress-467495-03.js
script regress-467495-04.js
script regress-467495-05.js
script regress-467495-06.js
script regress-468711.js
script regress-469547.js
script regress-469625-02.js
script regress-469625-03.js
-skip-if(Android) script regress-471373.js # bug xxx No test results reported
+script regress-471373.js
script regress-471660.js
script regress-472450-01.js
script regress-472450-02.js
script regress-472528-01.js
script regress-472528-02.js
script regress-472703.js
script regress-474769.js
skip-if(Android) script regress-474771.js
--- a/js/src/vm/RegExpObject-inl.h
+++ b/js/src/vm/RegExpObject-inl.h
@@ -84,16 +84,33 @@ HasRegExpMetaChars(const jschar *chars,
{
for (size_t i = 0; i < length; ++i) {
if (IsRegExpMetaChar(chars[i]))
return true;
}
return false;
}
+inline bool
+RegExpObject::startsWithAtomizedGreedyStar() const
+{
+ JSLinearString *source = getSource();
+
+ if (!source->isAtom())
+ return false;
+
+ if (source->length() < 3)
+ return false;
+
+ const jschar *chars = source->chars();
+ return chars[0] == detail::GreedyStarChars[0] &&
+ chars[1] == detail::GreedyStarChars[1] &&
+ chars[2] != '?';
+}
+
inline size_t *
RegExpObject::addressOfPrivateRefCount() const
{
JS_ASSERT(getPrivate());
return getPrivate()->addressOfRefCount();
}
inline void
@@ -252,55 +269,59 @@ detail::RegExpPrivate::getOrCreateCache(
return cache;
js_ReportOutOfMemory(cx);
return NULL;
}
inline bool
detail::RegExpPrivate::cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
+ RegExpPrivateCacheKind targetKind,
AlreadyIncRefed<RegExpPrivate> *result)
{
RegExpPrivateCache *cache = getOrCreateCache(cx);
if (!cache)
return false;
if (RegExpPrivateCache::Ptr p = cache->lookup(atom)) {
- NeedsIncRef<RegExpPrivate> cached(p->value);
- if (cached->getFlags() == flags) {
+ RegExpPrivateCacheValue &cacheValue = p->value;
+ if (cacheValue.kind() == targetKind && cacheValue.rep()->getFlags() == flags) {
+ NeedsIncRef<RegExpPrivate> cached(cacheValue.rep());
cached->incref(cx);
*result = AlreadyIncRefed<RegExpPrivate>(cached.get());
return true;
}
}
JS_ASSERT(result->null());
return true;
}
inline bool
-detail::RegExpPrivate::cacheInsert(JSContext *cx, JSAtom *atom, RegExpPrivate *priv)
+detail::RegExpPrivate::cacheInsert(JSContext *cx, JSAtom *atom, RegExpPrivateCacheKind kind,
+ RegExpPrivate *priv)
{
JS_ASSERT(priv);
/*
* Note: allocation performed since lookup may cause a garbage collection,
* so we have to re-lookup the cache (and inside the cache) after the
* allocation is performed.
*/
RegExpPrivateCache *cache = getOrCreateCache(cx);
if (!cache)
return false;
if (RegExpPrivateCache::AddPtr addPtr = cache->lookupForAdd(atom)) {
- /* We clobber existing entries with the same source (but different flags). */
- JS_ASSERT(addPtr->value->getFlags() != priv->getFlags());
- addPtr->value = priv;
+ /* We clobber existing entries with the same source (but different flags or kind). */
+ JS_ASSERT(addPtr->value.rep()->getFlags() != priv->getFlags() ||
+ addPtr->value.kind() != kind);
+ addPtr->value.reset(priv, kind);
} else {
- if (!cache->add(addPtr, atom, priv)) {
+ if (!cache->add(addPtr, atom, RegExpPrivateCacheValue(priv, kind))) {
js_ReportOutOfMemory(cx);
return false;
}
}
return true;
}
@@ -324,27 +345,27 @@ detail::RegExpPrivate::create(JSContext
* keep a refcount. The cache holds a "weak ref", where the
* |RegExpPrivate|'s deallocation decref will first cause it to
* remove itself from the cache.
*/
JSAtom *sourceAtom = &source->asAtom();
AlreadyIncRefed<RegExpPrivate> cached;
- if (!cacheLookup(cx, sourceAtom, flags, &cached))
+ if (!cacheLookup(cx, sourceAtom, flags, RegExpPrivateCache_ExecCapable, &cached))
return RetType(NULL);
if (cached)
return cached;
RegExpPrivate *priv = RegExpPrivate::createUncached(cx, source, flags, ts);
if (!priv)
return RetType(NULL);
- if (!cacheInsert(cx, sourceAtom, priv))
+ if (!cacheInsert(cx, sourceAtom, RegExpPrivateCache_ExecCapable, priv))
return RetType(NULL);
return RetType(priv);
}
/* This function should be deleted once bad Android platforms phase out. See bug 604774. */
inline bool
detail::RegExpPrivateCode::isJITRuntimeEnabled(JSContext *cx)
@@ -486,17 +507,17 @@ detail::RegExpPrivate::decref(JSContext
#endif
if (--refCount != 0)
return;
RegExpPrivateCache *cache;
if (source->isAtom() && (cache = cx->threadData()->getRegExpPrivateCache())) {
RegExpPrivateCache::Ptr ptr = cache->lookup(&source->asAtom());
- if (ptr && ptr->value == this)
+ if (ptr && ptr->value.rep() == this)
cache->remove(ptr);
}
#ifdef DEBUG
this->~RegExpPrivate();
memset(this, 0xcd, sizeof(*this));
cx->free_(this);
#else
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -52,16 +52,44 @@ using namespace js;
using js::detail::RegExpPrivate;
using js::detail::RegExpPrivateCode;
JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
+/* RegExpMatcher */
+
+bool
+RegExpMatcher::resetWithTestOptimized(RegExpObject *reobj)
+{
+ JS_ASSERT(reobj->startsWithAtomizedGreedyStar());
+
+ JSAtom *source = &reobj->getSource()->asAtom();
+ AlreadyIncRefed<RegExpPrivate> priv =
+ RegExpPrivate::createTestOptimized(cx, source, reobj->getFlags());
+ if (!priv)
+ return false;
+
+ /*
+ * Create a dummy RegExpObject to persist this RegExpPrivate until the next GC.
+ * Note that we give the ref we have to this new object.
+ */
+ RegExpObjectBuilder builder(cx);
+ RegExpObject *dummy = builder.build(priv);
+ if (!dummy) {
+ priv->decref(cx);
+ return false;
+ }
+
+ arc.reset(NeedsIncRef<RegExpPrivate>(priv.get()));
+ return true;
+}
+
/* RegExpObjectBuilder */
bool
RegExpObjectBuilder::getOrCreate()
{
if (reobj_)
return true;
@@ -463,33 +491,65 @@ js::ParseRegExpFlags(JSContext *cx, JSSt
return false;
}
}
#undef HANDLE_FLAG
}
return true;
}
-/* static */ RegExpPrivate *
+RegExpPrivate *
RegExpPrivate::createUncached(JSContext *cx, JSLinearString *source, RegExpFlag flags,
TokenStream *tokenStream)
{
RegExpPrivate *priv = cx->new_<RegExpPrivate>(source, flags);
if (!priv)
return NULL;
if (!priv->compile(cx, tokenStream)) {
Foreground::delete_(priv);
return NULL;
}
return priv;
}
AlreadyIncRefed<RegExpPrivate>
+RegExpPrivate::createTestOptimized(JSContext *cx, JSAtom *cacheKey, RegExpFlag flags)
+{
+ typedef AlreadyIncRefed<RegExpPrivate> RetType;
+
+ RetType cached;
+ if (!cacheLookup(cx, cacheKey, flags, RegExpPrivateCache_TestOptimized, &cached))
+ return RetType(NULL);
+
+ if (cached)
+ return cached;
+
+ /* Strip off the greedy star characters, create a new RegExpPrivate, and cache. */
+ JS_ASSERT(cacheKey->length() > JS_ARRAY_LENGTH(GreedyStarChars));
+ JSDependentString *stripped =
+ JSDependentString::new_(cx, cacheKey, cacheKey->chars() + JS_ARRAY_LENGTH(GreedyStarChars),
+ cacheKey->length() - JS_ARRAY_LENGTH(GreedyStarChars));
+ if (!stripped)
+ return RetType(NULL);
+
+ RegExpPrivate *priv = createUncached(cx, cacheKey, flags, NULL);
+ if (!priv)
+ return RetType(NULL);
+
+ if (!cacheInsert(cx, cacheKey, RegExpPrivateCache_TestOptimized, priv)) {
+ priv->decref(cx);
+ return RetType(NULL);
+ }
+
+ return RetType(priv);
+}
+
+AlreadyIncRefed<RegExpPrivate>
RegExpPrivate::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenStream *ts)
{
if (!opt)
return create(cx, str, RegExpFlag(0), ts);
RegExpFlag flags = RegExpFlag(0);
if (!ParseRegExpFlags(cx, opt, &flags))
return AlreadyIncRefed<RegExpPrivate>(NULL);
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -129,16 +129,18 @@ class RegExpObject : public ::JSObject
uintN flags = 0;
flags |= global() ? GlobalFlag : 0;
flags |= ignoreCase() ? IgnoreCaseFlag : 0;
flags |= multiline() ? MultilineFlag : 0;
flags |= sticky() ? StickyFlag : 0;
return RegExpFlag(flags);
}
+ inline bool startsWithAtomizedGreedyStar() const;
+
/* JIT only. */
inline size_t *addressOfPrivateRefCount() const;
/* Flags. */
inline void setIgnoreCase(bool enabled);
inline void setGlobal(bool enabled);
@@ -210,32 +212,36 @@ class RegExpObjectBuilder
JSContext *cx;
RegExpObject *reobj_;
bool getOrCreate();
bool getOrCreateClone(RegExpObject *proto);
RegExpObject *build(AlreadyIncRefed<RegExpPrivate> rep);
+ friend class RegExpMatcher;
+
public:
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL)
: cx(cx), reobj_(reobj)
{ }
RegExpObject *reobj() { return reobj_; }
RegExpObject *build(JSLinearString *str, RegExpFlag flags);
RegExpObject *build(RegExpObject *other);
/* Perform a VM-internal clone. */
RegExpObject *clone(RegExpObject *other, RegExpObject *proto);
};
namespace detail {
+static const jschar GreedyStarChars[] = {'.', '*'};
+
/* Abstracts away the gross |RegExpPrivate| backend details. */
class RegExpPrivateCode
{
#if ENABLE_YARR_JIT
typedef JSC::Yarr::BytecodePattern BytecodePattern;
typedef JSC::Yarr::ErrorCode ErrorCode;
typedef JSC::Yarr::JSGlobalData JSGlobalData;
typedef JSC::Yarr::YarrCodeBlock YarrCodeBlock;
@@ -301,16 +307,54 @@ class RegExpPrivateCode
inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
RegExpFlag flags);
inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount);
};
+enum RegExpPrivateCacheKind
+{
+ RegExpPrivateCache_TestOptimized,
+ RegExpPrivateCache_ExecCapable
+};
+
+class RegExpPrivateCacheValue
+{
+ union {
+ RegExpPrivate *rep_;
+ uintptr_t bits;
+ };
+
+ public:
+ RegExpPrivateCacheValue() : rep_(NULL) {}
+
+ RegExpPrivateCacheValue(RegExpPrivate *rep, RegExpPrivateCacheKind kind) {
+ reset(rep, kind);
+ }
+
+ RegExpPrivateCacheKind kind() const {
+ return (bits & 0x1)
+ ? RegExpPrivateCache_TestOptimized
+ : RegExpPrivateCache_ExecCapable;
+ }
+
+ RegExpPrivate *rep() {
+ return reinterpret_cast<RegExpPrivate *>(bits & ~uintptr_t(1));
+ }
+
+ void reset(RegExpPrivate *rep, RegExpPrivateCacheKind kind) {
+ rep_ = rep;
+ if (kind == RegExpPrivateCache_TestOptimized)
+ bits |= 0x1;
+ JS_ASSERT(this->kind() == kind);
+ }
+};
+
/*
* The "meat" of the builtin regular expression objects: it contains the
* mini-program that represents the source of the regular expression.
* Excepting refcounts, this is an immutable datastructure after
* compilation.
*
* Non-atomic refcounting is used, so single-thread invariants must be
* maintained. |RegExpPrivate|s are currently shared within a single
@@ -339,26 +383,30 @@ class RegExpPrivate
static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
static RegExpPrivate *
createUncached(JSContext *cx, JSLinearString *source, RegExpFlag flags,
TokenStream *tokenStream);
static RegExpPrivateCache *getOrCreateCache(JSContext *cx);
static bool cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
- AlreadyIncRefed<RegExpPrivate> *result);
- static bool cacheInsert(JSContext *cx, JSAtom *atom, RegExpPrivate *priv);
+ RegExpPrivateCacheKind kind, AlreadyIncRefed<RegExpPrivate> *result);
+ static bool cacheInsert(JSContext *cx, JSAtom *atom,
+ RegExpPrivateCacheKind kind, RegExpPrivate *priv);
public:
static AlreadyIncRefed<RegExpPrivate>
create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts);
static AlreadyIncRefed<RegExpPrivate>
create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts);
+ static AlreadyIncRefed<RegExpPrivate>
+ createTestOptimized(JSContext *cx, JSAtom *originalSource, RegExpFlag flags);
+
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output);
/* Mutators */
void incref(JSContext *cx);
void decref(JSContext *cx);
@@ -417,16 +465,18 @@ class RegExpMatcher
if (!priv)
return false;
arc.reset(NeedsIncRef<RegExpPrivate>(priv));
return true;
}
inline bool reset(JSLinearString *patstr, JSString *opt);
+ bool resetWithTestOptimized(RegExpObject *reobj);
+
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output) {
JS_ASSERT(!arc.null());
return arc.get()->execute(cx, chars, length, lastIndex, allocScope, output);
}
};
/*
--- a/js/src/yarr/YarrParser.h
+++ b/js/src/yarr/YarrParser.h
@@ -517,16 +517,21 @@ private:
*
* Helper for parseTokens(); checks for parse errors and non-greedy quantifiers.
*/
void parseQuantifier(bool lastTokenWasAnAtom, unsigned min, unsigned max)
{
ASSERT(!m_err);
ASSERT(min <= max);
+ if (min == unsigned(-1)) {
+ m_err = QuantifierTooLarge;
+ return;
+ }
+
if (lastTokenWasAnAtom)
m_delegate.quantifyAtom(min, max, !tryConsume('?'));
else
m_err = QuantifierWithoutAtom;
}
/*
* parseTokens():
--- a/js/src/yarr/YarrPattern.cpp
+++ b/js/src/yarr/YarrPattern.cpp
@@ -511,17 +511,17 @@ public:
}
}
void disjunction()
{
m_alternative = m_alternative->m_parent->addNewAlternative();
}
- unsigned setupAlternativeOffsets(PatternAlternative* alternative, unsigned currentCallFrameSize, unsigned initialInputPosition)
+ ErrorCode setupAlternativeOffsets(PatternAlternative* alternative, unsigned currentCallFrameSize, unsigned initialInputPosition, unsigned *callFrameSizeOut)
{
alternative->m_hasFixedSize = true;
unsigned currentInputPosition = initialInputPosition;
for (unsigned i = 0; i < alternative->m_terms.size(); ++i) {
PatternTerm& term = alternative->m_terms[i];
switch (term.type) {
@@ -562,75 +562,87 @@ public:
break;
case PatternTerm::TypeParenthesesSubpattern:
// Note: for fixed once parentheses we will ensure at least the minimum is available; others are on their own.
term.frameLocation = currentCallFrameSize;
if (term.quantityCount == 1 && !term.parentheses.isCopy) {
if (term.quantityType != QuantifierFixedCount)
currentCallFrameSize += YarrStackSpaceForBackTrackInfoParenthesesOnce;
- currentCallFrameSize = setupDisjunctionOffsets(term.parentheses.disjunction, currentCallFrameSize, currentInputPosition);
+ if (ErrorCode error = setupDisjunctionOffsets(term.parentheses.disjunction, currentCallFrameSize, currentInputPosition, ¤tCallFrameSize))
+ return error;
// If quantity is fixed, then pre-check its minimum size.
if (term.quantityType == QuantifierFixedCount)
currentInputPosition += term.parentheses.disjunction->m_minimumSize;
term.inputPosition = currentInputPosition;
} else if (term.parentheses.isTerminal) {
currentCallFrameSize += YarrStackSpaceForBackTrackInfoParenthesesTerminal;
- currentCallFrameSize = setupDisjunctionOffsets(term.parentheses.disjunction, currentCallFrameSize, currentInputPosition);
+ if (ErrorCode error = setupDisjunctionOffsets(term.parentheses.disjunction, currentCallFrameSize, currentInputPosition, ¤tCallFrameSize))
+ return error;
term.inputPosition = currentInputPosition;
} else {
term.inputPosition = currentInputPosition;
- setupDisjunctionOffsets(term.parentheses.disjunction, BASE_FRAME_SIZE, currentInputPosition);
+ unsigned dummy;
+ if (ErrorCode error = setupDisjunctionOffsets(term.parentheses.disjunction, BASE_FRAME_SIZE, currentInputPosition, &dummy))
+ return error;
currentCallFrameSize += YarrStackSpaceForBackTrackInfoParentheses;
}
// Fixed count of 1 could be accepted, if they have a fixed size *AND* if all alternatives are of the same length.
alternative->m_hasFixedSize = false;
break;
case PatternTerm::TypeParentheticalAssertion:
term.inputPosition = currentInputPosition;
term.frameLocation = currentCallFrameSize;
- currentCallFrameSize = setupDisjunctionOffsets(term.parentheses.disjunction, currentCallFrameSize + YarrStackSpaceForBackTrackInfoParentheticalAssertion, currentInputPosition);
+ if (ErrorCode error = setupDisjunctionOffsets(term.parentheses.disjunction, currentCallFrameSize + YarrStackSpaceForBackTrackInfoParentheticalAssertion, currentInputPosition, ¤tCallFrameSize))
+ return error;
break;
}
}
alternative->m_minimumSize = currentInputPosition - initialInputPosition;
- return currentCallFrameSize;
+ *callFrameSizeOut = currentCallFrameSize;
+ return NoError;
}
- unsigned setupDisjunctionOffsets(PatternDisjunction* disjunction, unsigned initialCallFrameSize, unsigned initialInputPosition)
+ ErrorCode setupDisjunctionOffsets(PatternDisjunction* disjunction, unsigned initialCallFrameSize, unsigned initialInputPosition, unsigned *maximumCallFrameSizeOut)
{
if ((disjunction != m_pattern.m_body) && (disjunction->m_alternatives.size() > 1))
initialCallFrameSize += YarrStackSpaceForBackTrackInfoAlternative;
unsigned minimumInputSize = UINT_MAX;
unsigned maximumCallFrameSize = 0;
bool hasFixedSize = true;
for (unsigned alt = 0; alt < disjunction->m_alternatives.size(); ++alt) {
PatternAlternative* alternative = disjunction->m_alternatives[alt];
- unsigned currentAlternativeCallFrameSize = setupAlternativeOffsets(alternative, initialCallFrameSize, initialInputPosition);
+ unsigned currentAlternativeCallFrameSize;
+ if (ErrorCode error = setupAlternativeOffsets(alternative, initialCallFrameSize, initialInputPosition, ¤tAlternativeCallFrameSize))
+ return error;
minimumInputSize = std::min(minimumInputSize, alternative->m_minimumSize);
maximumCallFrameSize = std::max(maximumCallFrameSize, currentAlternativeCallFrameSize);
hasFixedSize &= alternative->m_hasFixedSize;
}
- ASSERT(minimumInputSize != UINT_MAX);
+ if (minimumInputSize == UINT_MAX)
+ return PatternTooLarge;
+
ASSERT(maximumCallFrameSize >= initialCallFrameSize);
disjunction->m_hasFixedSize = hasFixedSize;
disjunction->m_minimumSize = minimumInputSize;
disjunction->m_callFrameSize = maximumCallFrameSize;
- return maximumCallFrameSize;
+ *maximumCallFrameSizeOut = maximumCallFrameSize;
+ return NoError;
}
- void setupOffsets()
+ ErrorCode setupOffsets()
{
- setupDisjunctionOffsets(m_pattern.m_body, BASE_FRAME_SIZE, 0);
+ unsigned dummy;
+ return setupDisjunctionOffsets(m_pattern.m_body, BASE_FRAME_SIZE, 0, &dummy);
}
// This optimization identifies sets of parentheses that we will never need to backtrack.
// In these cases we do not need to store state from prior iterations.
// We can presently avoid backtracking for:
// * where the parens are at the end of the regular expression (last term in any of the
// alternatives of the main body disjunction).
// * where the parens are non-capturing, and quantified unbounded greedy (*).
@@ -713,17 +725,18 @@ ErrorCode YarrPattern::compile(const USt
ASSERT(!error);
ASSERT(numSubpatterns == m_numSubpatterns);
}
constructor.checkForTerminalParentheses();
constructor.optimizeBOL();
- constructor.setupOffsets();
+ if (ErrorCode error = constructor.setupOffsets())
+ return error;
return NoError;
}
YarrPattern::YarrPattern(const UString& pattern, bool ignoreCase, bool multiline, ErrorCode* error)
: m_ignoreCase(ignoreCase)
, m_multiline(multiline)
, m_containsBackreferences(false)
--- a/js/xpconnect/src/dom_quickstubs.qsconf
+++ b/js/xpconnect/src/dom_quickstubs.qsconf
@@ -225,16 +225,17 @@ members = [
'nsIDOMHTMLDocument.forms',
'nsIDOMHTMLDocument.cookie',
'nsIDOMHTMLDocument.images',
'nsIDOMHTMLDocument.write',
'nsIDOMHTMLDocument.writeln',
'nsIDOMHTMLDocument.domain',
'nsIDOMHTMLDocument.getSelection',
'nsIDOMHTMLDocument.designMode',
+ 'nsIDOMHTMLDocument.head',
'nsIDOMHTMLElement.*',
'nsIDOMHTMLFormElement.elements',
'nsIDOMHTMLFormElement.name',
'nsIDOMHTMLFormElement.submit',
'nsIDOMHTMLFormElement.length',
'nsIDOMHTMLFormElement.target',
'nsIDOMHTMLFormElement.action',
'nsIDOMHTMLFrameElement.src',
@@ -645,17 +646,23 @@ customMethodCalls = {
'code': nsIDOMHTMLDocument_Write_customMethodCallCode % 'Write'
},
'nsIDOMHTMLDocument_Writeln': {
'thisType': 'nsHTMLDocument',
'code': nsIDOMHTMLDocument_Write_customMethodCallCode % 'Writeln'
},
'nsIDOMHTMLDocument_GetBody': {
'thisType': 'nsHTMLDocument',
- 'code': ' nsIContent *result = self->GetBody(&rv);'
+ 'code': ' nsIContent *result = self->GetBody();',
+ 'canFail': False
+ },
+ 'nsIDOMHTMLDocument_GetHead': {
+ 'thisType': 'nsHTMLDocument',
+ 'code': ' nsIContent *result = self->GetHead();',
+ 'canFail': False
},
'nsIDOMHTMLDocument_GetElementsByName': {
'thisType': 'nsHTMLDocument',
'code': ' nsRefPtr<nsContentList> result = '
'self->GetElementsByName(arg0);',
'canFail': False
},
'nsIDOMStorage_Clear': {
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2214,16 +2214,21 @@ DocumentViewerImpl::CreateStyleSet(nsIDo
}
}
sheet = nsLayoutStylesheetCache::FormsSheet();
if (sheet) {
styleSet->PrependStyleSheet(nsStyleSet::eAgentSheet, sheet);
}
+ sheet = nsLayoutStylesheetCache::FullScreenOverrideSheet();
+ if (sheet) {
+ styleSet->PrependStyleSheet(nsStyleSet::eOverrideSheet, sheet);
+ }
+
// Make sure to clone the quirk sheet so that it can be usefully
// enabled/disabled as needed.
nsRefPtr<nsCSSStyleSheet> quirkClone;
nsCSSStyleSheet* quirkSheet;
if (!nsLayoutStylesheetCache::UASheet() ||
!(quirkSheet = nsLayoutStylesheetCache::QuirkSheet()) ||
!(quirkClone = quirkSheet->Clone(nsnull, nsnull, nsnull, nsnull)) ||
!sheet) {
new file mode 100644
--- /dev/null
+++ b/layout/style/full-screen-override.css
@@ -0,0 +1,56 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+*|*:-moz-full-screen-ancestor {
+ /* Ancestors of a full-screen element should not induce stacking contexts
+ that would prevent the full-screen element from being on top. */
+ z-index: auto;
+ /* Ancestors of a full-screen element should not be partially transparent,
+ since that would apply to the full-screen element and make the page visible
+ behind it. It would also create a pseudo-stacking-context that would let content
+ draw on top of the full-screen element. */
+ opacity: 1;
+ /* Ancestors of a full-screen element should not apply SVG masking, clipping, or
+ filtering, since that would affect the full-screen element and create a pseudo-
+ stacking context. */
+ mask: none;
+ clip: auto;
+ filter: none;
+ -moz-transform: none;
+}
+
--- a/layout/style/jar.mn
+++ b/layout/style/jar.mn
@@ -1,10 +1,11 @@
toolkit.jar:
* res/ua.css (ua.css)
res/html.css (html.css)
res/quirk.css (quirk.css)
+ res/full-screen-override.css (full-screen-override.css)
res/viewsource.css (viewsource.css)
* res/forms.css (forms.css)
res/arrow.gif (arrow.gif)
res/arrowd.gif (arrowd.gif)
% resource gre-resources %res/
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -150,16 +150,26 @@ nsLayoutStylesheetCache::QuirkSheet()
{
EnsureGlobal();
if (!gStyleCache)
return nsnull;
return gStyleCache->mQuirkSheet;
}
+nsCSSStyleSheet*
+nsLayoutStylesheetCache::FullScreenOverrideSheet()
+{
+ EnsureGlobal();
+ if (!gStyleCache)
+ return nsnull;
+
+ return gStyleCache->mFullScreenOverrideSheet;
+}
+
void
nsLayoutStylesheetCache::Shutdown()
{
NS_IF_RELEASE(gCSSLoader);
NS_IF_RELEASE(gStyleCache);
}
nsLayoutStylesheetCache::nsLayoutStylesheetCache()
@@ -186,16 +196,23 @@ nsLayoutStylesheetCache::nsLayoutStylesh
}
NS_ASSERTION(mUASheet, "Could not load ua.css");
NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/quirk.css");
if (uri) {
LoadSheet(uri, mQuirkSheet, true);
}
NS_ASSERTION(mQuirkSheet, "Could not load quirk.css");
+
+ NS_NewURI(getter_AddRefs(uri), "resource://gre-resources/full-screen-override.css");
+ if (uri) {
+ LoadSheet(uri, mFullScreenOverrideSheet, true);
+ }
+ NS_ASSERTION(mFullScreenOverrideSheet, "Could not load full-screen-override.css");
+
}
void
nsLayoutStylesheetCache::EnsureGlobal()
{
if (gStyleCache) return;
gStyleCache = new nsLayoutStylesheetCache();
--- a/layout/style/nsLayoutStylesheetCache.h
+++ b/layout/style/nsLayoutStylesheetCache.h
@@ -59,16 +59,17 @@ class nsLayoutStylesheetCache
NS_DECL_NSIOBSERVER
static nsCSSStyleSheet* ScrollbarsSheet();
static nsCSSStyleSheet* FormsSheet();
static nsCSSStyleSheet* UserContentSheet();
static nsCSSStyleSheet* UserChromeSheet();
static nsCSSStyleSheet* UASheet();
static nsCSSStyleSheet* QuirkSheet();
+ static nsCSSStyleSheet* FullScreenOverrideSheet();
static void Shutdown();
private:
nsLayoutStylesheetCache();
~nsLayoutStylesheetCache() {}
static void EnsureGlobal();
@@ -80,11 +81,12 @@ private:
static nsLayoutStylesheetCache* gStyleCache;
static mozilla::css::Loader* gCSSLoader;
nsRefPtr<nsCSSStyleSheet> mScrollbarsSheet;
nsRefPtr<nsCSSStyleSheet> mFormsSheet;
nsRefPtr<nsCSSStyleSheet> mUserContentSheet;
nsRefPtr<nsCSSStyleSheet> mUserChromeSheet;
nsRefPtr<nsCSSStyleSheet> mUASheet;
nsRefPtr<nsCSSStyleSheet> mQuirkSheet;
+ nsRefPtr<nsCSSStyleSheet> mFullScreenOverrideSheet;
};
#endif
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -255,33 +255,16 @@
/* If there is a full-screen element that is not the root then
we should hide the viewport scrollbar. We exclude the chrome
document to prevent reframing of contained plugins. */
:not(xul|*):root:-moz-full-screen-ancestor {
overflow: hidden !important;
}
-*|*:-moz-full-screen-ancestor {
- /* Ancestors of a full-screen element should not induce stacking contexts
- that would prevent the full-screen element from being on top. */
- z-index:auto !important;
- /* Ancestors of a full-screen element should not be partially transparent,
- since that would apply to the full-screen element and make the page visible
- behind it. It would also create a pseudo-stacking-context that would let content
- draw on top of the full-screen element. */
- opacity:1 !important;
- /* Ancestors of a full-screen element should not apply SVG masking, clipping, or
- filtering, since that would affect the full-screen element and create a pseudo-
- stacking context. */
- mask:none !important;
- clip:none !important;
- filter:none !important;
-}
-
/* XML parse error reporting */
parsererror|parsererror {
display: block;
font-family: sans-serif;
font-weight: bold;
white-space: pre;
margin: 1em;
--- a/memory/jemalloc/jemalloc.c
+++ b/memory/jemalloc/jemalloc.c
@@ -1079,16 +1079,62 @@ struct arena_s {
* Data.
*/
#ifndef MOZ_MEMORY_NARENAS_DEFAULT_ONE
/* Number of CPUs. */
static unsigned ncpus;
#endif
+/*
+ * When MALLOC_STATIC_SIZES is defined most of the parameters
+ * controlling the malloc behavior are defined as compile-time constants
+ * for best performance and cannot be altered at runtime.
+ */
+#define MALLOC_STATIC_SIZES 1
+
+#ifdef MALLOC_STATIC_SIZES
+
+/*
+ * VM page size. It must divide the runtime CPU page size or the code
+ * will abort.
+ */
+#define pagesize_2pow ((size_t) 12)
+#define pagesize ((size_t) 1 << pagesize_2pow)
+#define pagesize_mask (pagesize - 1)
+
+/* Various quantum-related settings. */
+
+#define QUANTUM_DEFAULT ((size_t) 1 << QUANTUM_2POW_MIN)
+static const size_t quantum = QUANTUM_DEFAULT;
+static const size_t quantum_mask = QUANTUM_DEFAULT - 1;
+
+/* Various bin-related settings. */
+
+static const size_t small_min = (QUANTUM_DEFAULT >> 1) + 1;
+static const size_t small_max = (size_t) SMALL_MAX_DEFAULT;
+
+/* Max size class for bins. */
+static const size_t bin_maxclass = pagesize >> 1;
+
+ /* Number of (2^n)-spaced tiny bins. */
+static const unsigned ntbins = (unsigned)
+ (QUANTUM_2POW_MIN - TINY_MIN_2POW);
+
+ /* Number of quantum-spaced bins. */
+static const unsigned nqbins = (unsigned)
+ (SMALL_MAX_DEFAULT >> QUANTUM_2POW_MIN);
+
+/* Number of (2^n)-spaced sub-page bins. */
+static const unsigned nsbins = (unsigned)
+ (pagesize_2pow -
+ SMALL_MAX_2POW_DEFAULT - 1);
+
+#else /* !MALLOC_STATIC_SIZES */
+
/* VM page size. */
static size_t pagesize;
static size_t pagesize_mask;
static size_t pagesize_2pow;
/* Various bin-related settings. */
static size_t bin_maxclass; /* Max size class for bins. */
static unsigned ntbins; /* Number of (2^n)-spaced tiny bins. */
@@ -1096,22 +1142,51 @@ static unsigned nqbins; /* Number of qu
static unsigned nsbins; /* Number of (2^n)-spaced sub-page bins. */
static size_t small_min;
static size_t small_max;
/* Various quantum-related settings. */
static size_t quantum;
static size_t quantum_mask; /* (quantum - 1). */
+#endif
+
/* Various chunk-related settings. */
+
+/*
+ * Compute the header size such that it is large enough to contain the page map
+ * and enough nodes for the worst case: one node per non-header page plus one
+ * extra for situations where we briefly have one more node allocated than we
+ * will need.
+ */
+#define calculate_arena_header_size() \
+ (sizeof(arena_chunk_t) + sizeof(arena_chunk_map_t) * (chunk_npages - 1))
+
+#define calculate_arena_header_pages() \
+ ((calculate_arena_header_size() >> pagesize_2pow) + \
+ ((calculate_arena_header_size() & pagesize_mask) ? 1 : 0))
+
+/* Max size class for arenas. */
+#define calculate_arena_maxclass() \
+ (chunksize - (arena_chunk_header_npages << pagesize_2pow))
+
+#ifdef MALLOC_STATIC_SIZES
+#define CHUNKSIZE_DEFAULT ((size_t) 1 << CHUNK_2POW_DEFAULT)
+static const size_t chunksize = CHUNKSIZE_DEFAULT;
+static const size_t chunksize_mask =CHUNKSIZE_DEFAULT - 1;
+static const size_t chunk_npages = CHUNKSIZE_DEFAULT >> pagesize_2pow;
+#define arena_chunk_header_npages calculate_arena_header_pages()
+#define arena_maxclass calculate_arena_maxclass()
+#else
static size_t chunksize;
static size_t chunksize_mask; /* (chunksize - 1). */
static size_t chunk_npages;
static size_t arena_chunk_header_npages;
static size_t arena_maxclass; /* Max size class for arenas. */
+#endif
/********/
/*
* Chunks.
*/
#ifdef MALLOC_VALIDATE
static malloc_rtree_t *chunk_rtree;
@@ -1213,19 +1288,25 @@ static bool opt_abort = false;
static bool opt_junk = false;
#endif
#endif
static size_t opt_dirty_max = DIRTY_MAX_DEFAULT;
#ifdef MALLOC_BALANCE
static uint64_t opt_balance_threshold = BALANCE_THRESHOLD_DEFAULT;
#endif
static bool opt_print_stats = false;
+#ifdef MALLOC_STATIC_SIZES
+#define opt_quantum_2pow QUANTUM_2POW_MIN
+#define opt_small_max_2pow SMALL_MAX_2POW_DEFAULT
+#define opt_chunk_2pow CHUNK_2POW_DEFAULT
+#else
static size_t opt_quantum_2pow = QUANTUM_2POW_MIN;
static size_t opt_small_max_2pow = SMALL_MAX_2POW_DEFAULT;
static size_t opt_chunk_2pow = CHUNK_2POW_DEFAULT;
+#endif
#ifdef MALLOC_PAGEFILE
static bool opt_pagefile = false;
#endif
#ifdef MALLOC_UTRACE
static bool opt_utrace = false;
#endif
#ifdef MALLOC_SYSV
static bool opt_sysv = false;
@@ -5554,41 +5635,44 @@ malloc_init_hard(void)
/* Get page size and number of CPUs */
#ifdef MOZ_MEMORY_WINDOWS
{
SYSTEM_INFO info;
GetSystemInfo(&info);
result = info.dwPageSize;
- pagesize = (unsigned) result;
-
#ifndef MOZ_MEMORY_NARENAS_DEFAULT_ONE
ncpus = info.dwNumberOfProcessors;
#endif
}
#else
#ifndef MOZ_MEMORY_NARENAS_DEFAULT_ONE
ncpus = malloc_ncpus();
#endif
result = sysconf(_SC_PAGESIZE);
assert(result != -1);
-
- pagesize = (unsigned) result;
-#endif
-
- /*
- * We assume that pagesize is a power of 2 when calculating
- * pagesize_mask and pagesize_2pow.
- */
+#endif
+
+ /* We assume that the page size is a power of 2. */
assert(((result - 1) & result) == 0);
- pagesize_mask = result - 1;
+#ifdef MALLOC_STATIC_SIZES
+ if (pagesize % (size_t) result) {
+ _malloc_message(_getprogname(),
+ "Compile-time page size does not divide the runtime one.\n",
+ "", "");
+ abort();
+ }
+#else
+ pagesize = (size_t) result;
+ pagesize_mask = (size_t) result - 1;
pagesize_2pow = ffs((int)result) - 1;
-
+#endif
+
#ifdef MALLOC_PAGEFILE
/*
* Determine where to create page files. It is insufficient to
* unconditionally use P_tmpdir (typically "/tmp"), since for some
* operating systems /tmp is a separate filesystem that is rather small.
* Therefore prefer, in order, the following locations:
*
* 1) MALLOC_TMPDIR
@@ -5727,30 +5811,32 @@ MALLOC_OUT:
#ifdef MALLOC_FILL
case 'j':
opt_junk = false;
break;
case 'J':
opt_junk = true;
break;
#endif
+#ifndef MALLOC_STATIC_SIZES
case 'k':
/*
* Chunks always require at least one
* header page, so chunks can never be
* smaller than two pages.
*/
if (opt_chunk_2pow > pagesize_2pow + 1)
opt_chunk_2pow--;
break;
case 'K':
if (opt_chunk_2pow + 1 <
(sizeof(size_t) << 3))
opt_chunk_2pow++;
break;
+#endif
case 'n':
opt_narenas_lshift--;
break;
case 'N':
opt_narenas_lshift++;
break;
#ifdef MALLOC_PAGEFILE
case 'o':
@@ -5763,16 +5849,17 @@ MALLOC_OUT:
break;
#endif
case 'p':
opt_print_stats = false;
break;
case 'P':
opt_print_stats = true;
break;
+#ifndef MALLOC_STATIC_SIZES
case 'q':
if (opt_quantum_2pow > QUANTUM_2POW_MIN)
opt_quantum_2pow--;
break;
case 'Q':
if (opt_quantum_2pow < pagesize_2pow -
1)
opt_quantum_2pow++;
@@ -5782,16 +5869,17 @@ MALLOC_OUT:
QUANTUM_2POW_MIN)
opt_small_max_2pow--;
break;
case 'S':
if (opt_small_max_2pow < pagesize_2pow
- 1)
opt_small_max_2pow++;
break;
+#endif
#ifdef MALLOC_UTRACE
case 'u':
opt_utrace = false;
break;
case 'U':
opt_utrace = true;
break;
#endif
@@ -5844,16 +5932,17 @@ MALLOC_OUT:
#if (!defined(MOZ_MEMORY_WINDOWS) && !defined(MOZ_MEMORY_DARWIN) && !defined(MOZ_MEMORY_ANDROID))
/* Prevent potential deadlock on malloc locks after fork. */
/* XXX on Android there is no pthread_atfork, so we specifically
call _malloc_prefork and _malloc_postfork in process_util_linux.cc */
pthread_atfork(_malloc_prefork, _malloc_postfork, _malloc_postfork);
#endif
+#ifndef MALLOC_STATIC_SIZES
/* Set variables according to the value of opt_small_max_2pow. */
if (opt_small_max_2pow < opt_quantum_2pow)
opt_small_max_2pow = opt_quantum_2pow;
small_max = (1U << opt_small_max_2pow);
/* Set bin-related variables. */
bin_maxclass = (pagesize >> 1);
assert(opt_quantum_2pow >= TINY_MIN_2POW);
@@ -5870,33 +5959,20 @@ MALLOC_OUT:
else
small_min = 1;
assert(small_min <= quantum);
/* Set variables according to the value of opt_chunk_2pow. */
chunksize = (1LU << opt_chunk_2pow);
chunksize_mask = chunksize - 1;
chunk_npages = (chunksize >> pagesize_2pow);
- {
- size_t header_size;
-
- /*
- * Compute the header size such that it is large
- * enough to contain the page map and enough nodes for the
- * worst case: one node per non-header page plus one extra for
- * situations where we briefly have one more node allocated
- * than we will need.
- */
- header_size = sizeof(arena_chunk_t) +
- (sizeof(arena_chunk_map_t) * (chunk_npages - 1));
- arena_chunk_header_npages = (header_size >> pagesize_2pow) +
- ((header_size & pagesize_mask) != 0);
- }
- arena_maxclass = chunksize - (arena_chunk_header_npages <<
- pagesize_2pow);
+
+ arena_chunk_header_npages = calculate_arena_header_pages();
+ arena_maxclass = calculate_arena_maxclass();
+#endif
#ifdef JEMALLOC_USES_MAP_ALIGN
/*
* When using MAP_ALIGN, the alignment parameter must be a power of two
* multiple of the system pagesize, or mmap will fail.
*/
assert((chunksize % pagesize) == 0);
assert((1 << (ffs(chunksize / pagesize) - 1)) == (chunksize/pagesize));
--- a/mobile/xul/installer/package-manifest.in
+++ b/mobile/xul/installer/package-manifest.in
@@ -273,17 +273,16 @@
@BINPATH@/components/xpcom_threads.xpt
@BINPATH@/components/xpcom_xpti.xpt
@BINPATH@/components/xpconnect.xpt
@BINPATH@/components/xulapp.xpt
@BINPATH@/components/xul.xpt
@BINPATH@/components/xuldoc.xpt
@BINPATH@/components/xultmpl.xpt
@BINPATH@/components/zipwriter.xpt
-@BINPATH@/components/openwebapps.xpt
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
@BINPATH@/components/FeedProcessor.manifest
@BINPATH@/components/FeedProcessor.js
@BINPATH@/components/BrowserFeeds.manifest
@BINPATH@/components/FeedConverter.js
@@ -327,17 +326,16 @@
@BINPATH@/components/GPSDGeolocationProvider.js
@BINPATH@/components/nsSidebar.manifest
@BINPATH@/components/nsSidebar.js
@BINPATH@/components/extensions.manifest
@BINPATH@/components/addonManager.js
@BINPATH@/components/amContentHandler.js
@BINPATH@/components/amWebInstallListener.js
@BINPATH@/components/nsBlocklistService.js
-@BINPATH@/components/OpenWebapps.manifest
#ifdef MOZ_UPDATER
@BINPATH@/components/nsUpdateService.manifest
@BINPATH@/components/nsUpdateService.js
@BINPATH@/components/nsUpdateServiceStub.js
#endif
@BINPATH@/components/nsUpdateTimerManager.manifest
@BINPATH@/components/nsUpdateTimerManager.js
@@ -603,17 +601,16 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
@BINPATH@/components/HelperAppDialog.js
@BINPATH@/components/LoginManager.js
@BINPATH@/components/LoginManagerPrompter.js
@BINPATH@/components/MobileComponents.manifest
@BINPATH@/components/MobileComponents.xpt
@BINPATH@/components/PromptService.js
@BINPATH@/components/SessionStore.js
@BINPATH@/components/Sidebar.js
-@BINPATH@/components/OpenWebapps.js
#ifdef MOZ_SAFE_BROWSING
@BINPATH@/components/SafeBrowsing.js
#endif
#ifdef MOZ_UPDATER
@BINPATH@/components/UpdatePrompt.js
#endif
@BINPATH@/components/XPIDialogService.js
@BINPATH@/components/CapturePicker.js
--- a/netwerk/base/src/nsURLHelper.cpp
+++ b/netwerk/base/src/nsURLHelper.cpp
@@ -803,17 +803,17 @@ net_FindStringEnd(const nsCString& flatS
static PRUint32
net_FindMediaDelimiter(const nsCString& flatStr,
PRUint32 searchStart,
char delimiter)
{
do {
// searchStart points to the spot from which we should start looking
// for the delimiter.
- const char delimStr[] = { delimiter, '"', '\'', '\0' };
+ const char delimStr[] = { delimiter, '"', '\0' };
PRUint32 curDelimPos = flatStr.FindCharInSet(delimStr, searchStart);
if (curDelimPos == PRUint32(kNotFound))
return flatStr.Length();
char ch = flatStr.CharAt(curDelimPos);
if (ch == delimiter) {
// Found delimiter
return curDelimPos;
@@ -888,17 +888,17 @@ net_ParseMediaType(const nsACString &aMe
} while (curParamStart < flatStr.Length());
}
if (typeHasCharset) {
// Trim LWS leading and trailing whitespace from charset. We include
// '(' in the trailing trim set to catch media-type comments, which are
// not at all standard, but may occur in rare cases.
charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
- if (*charset == '"' || *charset == '\'') {
+ if (*charset == '"') {
charsetEnd =
start + net_FindStringEnd(flatStr, charset - start, *charset);
charset++;
NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
} else {
charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";(");
}
}
--- a/netwerk/test/unit/test_extract_charset_from_content_type.js
+++ b/netwerk/test/unit/test_extract_charset_from_content_type.js
@@ -116,50 +116,50 @@ function run_test() {
hadCharset =
netutil.extractCharsetFromContentType('text/html; charset="ISO-8859-1"',
charset, charsetStart, charsetEnd);
check(true, "ISO-8859-1", 9, 31);
hadCharset =
netutil.extractCharsetFromContentType("text/html; charset='ISO-8859-1'",
charset, charsetStart, charsetEnd);
- check(true, "ISO-8859-1", 9, 31);
+ check(true, "'ISO-8859-1'", 9, 31);
hadCharset =
- netutil.extractCharsetFromContentType("text/html; charset='ISO-8859-1', text/html",
+ netutil.extractCharsetFromContentType("text/html; charset=\"ISO-8859-1\", text/html",
charset, charsetStart, charsetEnd);
check(true, "ISO-8859-1", 9, 31);
hadCharset =
- netutil.extractCharsetFromContentType("text/html; charset='ISO-8859-1', text/html; charset=UTF8",
+ netutil.extractCharsetFromContentType("text/html; charset=\"ISO-8859-1\", text/html; charset=UTF8",
charset, charsetStart, charsetEnd);
check(true, "UTF8", 42, 56);
hadCharset =
netutil.extractCharsetFromContentType("text/html; charset=ISO-8859-1, TEXT/HTML",
charset, charsetStart, charsetEnd);
check(true, "ISO-8859-1", 9, 29);
hadCharset =
netutil.extractCharsetFromContentType("text/html; charset=ISO-8859-1, TEXT/plain",
charset, charsetStart, charsetEnd);
check(false, "", 41, 41);
hadCharset =
- netutil.extractCharsetFromContentType("text/plain, TEXT/HTML; charset='ISO-8859-1', text/html, TEXT/HTML",
+ netutil.extractCharsetFromContentType("text/plain, TEXT/HTML; charset=\"ISO-8859-1\", text/html, TEXT/HTML",
charset, charsetStart, charsetEnd);
check(true, "ISO-8859-1", 21, 43);
hadCharset =
- netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset=\'ISO-8859-1\'; param2="charset=UTF16", text/html, TEXT/HTML',
+ netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML',
charset, charsetStart, charsetEnd);
check(true, "ISO-8859-1", 43, 65);
hadCharset =
- netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset=\'ISO-8859-1\'; param2=charset=UTF16, text/html, TEXT/HTML',
+ netutil.extractCharsetFromContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML',
charset, charsetStart, charsetEnd);
check(true, "ISO-8859-1", 41, 63);
hadCharset =
netutil.extractCharsetFromContentType("text/plain; param= , text/html",
charset, charsetStart, charsetEnd);
check(false, "", 30, 30);
--- a/netwerk/test/unit/test_parse_content_type.js
+++ b/netwerk/test/unit/test_parse_content_type.js
@@ -51,16 +51,17 @@ function check(aType, aCharset, aHadChar
do_check_eq(aCharset, charset.value);
do_check_eq(aHadCharset, hadCharset.value);
reset();
}
function run_test() {
var netutil = Components.classes["@mozilla.org/network/util;1"]
.getService(Components.interfaces.nsINetUtil);
+
type = netutil.parseContentType("text/html", charset, hadCharset);
check("text/html", "", false);
type = netutil.parseContentType("TEXT/HTML", charset, hadCharset);
check("text/html", "", false);
type = netutil.parseContentType("text/html, text/html", charset, hadCharset);
check("text/html", "", false);
@@ -83,39 +84,39 @@ function run_test() {
check("text/html", "ISO-8859-1", true);
type = netutil.parseContentType('text/html; charset="ISO-8859-1"',
charset, hadCharset);
check("text/html", "ISO-8859-1", true);
type = netutil.parseContentType("text/html; charset='ISO-8859-1'",
charset, hadCharset);
- check("text/html", "ISO-8859-1", true);
+ check("text/html", "'ISO-8859-1'", true);
- type = netutil.parseContentType("text/html; charset='ISO-8859-1', text/html",
+ type = netutil.parseContentType("text/html; charset=\"ISO-8859-1\", text/html",
charset, hadCharset);
check("text/html", "ISO-8859-1", true);
- type = netutil.parseContentType("text/html; charset='ISO-8859-1', text/html; charset=UTF8",
+ type = netutil.parseContentType("text/html; charset=\"ISO-8859-1\", text/html; charset=UTF8",
charset, hadCharset);
check("text/html", "UTF8", true);
type = netutil.parseContentType("text/html; charset=ISO-8859-1, TEXT/HTML", charset, hadCharset);
check("text/html", "ISO-8859-1", true);
type = netutil.parseContentType("text/html; charset=ISO-8859-1, TEXT/plain", charset, hadCharset);
check("text/plain", "", true);
- type = netutil.parseContentType("text/plain, TEXT/HTML; charset='ISO-8859-1', text/html, TEXT/HTML", charset, hadCharset);
+ type = netutil.parseContentType("text/plain, TEXT/HTML; charset=ISO-8859-1, text/html, TEXT/HTML", charset, hadCharset);
check("text/html", "ISO-8859-1", true);
- type = netutil.parseContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset=\'ISO-8859-1\'; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
+ type = netutil.parseContentType('text/plain, TEXT/HTML; param="charset=UTF8"; charset="ISO-8859-1"; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
check("text/html", "ISO-8859-1", true);
- type = netutil.parseContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset=\'ISO-8859-1\'; param2=charset=UTF16, text/html, TEXT/HTML', charset, hadCharset);
+ type = netutil.parseContentType('text/plain, TEXT/HTML; param=charset=UTF8; charset="ISO-8859-1"; param2=charset=UTF16, text/html, TEXT/HTML', charset, hadCharset);
check("text/html", "ISO-8859-1", true);
type = netutil.parseContentType("text/plain; param= , text/html", charset, hadCharset);
check("text/html", "", false);
type = netutil.parseContentType('text/plain; param=", text/html"', charset, hadCharset);
check("text/plain", "", false);
@@ -129,9 +130,18 @@ function run_test() {
check("text/plain", "", false);
type = netutil.parseContentType('text/plain charset=UTF8', charset, hadCharset);
check("text/plain", "", false);
type = netutil.parseContentType('text/plain, TEXT/HTML; param="charset=UTF8"; ; param2="charset=UTF16", text/html, TEXT/HTML', charset, hadCharset);
check("text/html", "", false);
+ // Bug 700589
+
+ // check that single quote doesn't confuse parsing of subsequent parameters
+ type = netutil.parseContentType("text/plain; x='; charset=\"UTF-8\"", charset, hadCharset);
+ check("text/plain", "UTF-8", true);
+
+ // check that single quotes do not get removed from extracted charset
+ type = netutil.parseContentType("text/plain; charset='UTF-8'", charset, hadCharset);
+ check("text/plain", "'UTF-8'", true);
}
--- a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
+++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js
@@ -682,16 +682,24 @@ SpecialPowersAPI.prototype = {
},
clipboardCopyString: function(preExpectedVal) {
var cbHelperSvc = Components.classes["@mozilla.org/widget/clipboardhelper;1"].
getService(Components.interfaces.nsIClipboardHelper);
cbHelperSvc.copyString(preExpectedVal);
},
+ supportsSelectionClipboard: function() {
+ if (this._cb == null) {
+ this._cb = Components.classes["@mozilla.org/widget/clipboard;1"].
+ getService(Components.interfaces.nsIClipboard);
+ }
+ return this._cb.supportsSelectionClipboard();
+ },
+
snapshotWindow: function (win, withCaret) {
var el = this.window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
el.width = win.innerWidth;
el.height = win.innerHeight;
var ctx = el.getContext("2d");
var flags = 0;
ctx.drawWindow(win, win.scrollX, win.scrollY,
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/Makefile.in
@@ -0,0 +1,73 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozbase.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Andrew Halberstadt <halbersa@gmail.com> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH = ../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = testing_mozbase
+
+include $(topsrcdir)/config/rules.mk
+
+# Harness packages from the srcdir;
+# python packages to be installed IN INSTALLATION ORDER.
+# Packages later in the list can depend only on packages earlier in the list.
+MOZBASE_PACKAGES = \
+ manifestdestiny \
+ mozdevice \
+ mozhttpd \
+ mozinfo \
+ mozinstall \
+ mozlog \
+ mozprocess \
+ mozprofile \
+ mozrunner \
+ $(NULL)
+
+MOZBASE_EXTRAS = \
+ setup_development.py \
+ README \
+ $(NULL)
+
+stage-package: PKG_STAGE = $(DIST)/test-package-stage
+stage-package:
+ $(NSINSTALL) -D $(PKG_STAGE)/mozbase
+ @(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - $(MOZBASE_PACKAGES)) | (cd $(PKG_STAGE)/mozbase && tar -xf -)
+ @(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - $(MOZBASE_EXTRAS)) | (cd $(PKG_STAGE)/mozbase && tar -xf -)
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/README
@@ -0,0 +1,7 @@
+This is the git repo for the mozbase suite of python utilities.
+
+Learn more about mozbase here: https://wiki.mozilla.org/Auto-tools/Projects/MozBase
+
+Bugs live at https://bugzilla.mozilla.org/buglist.cgi?resolution=---&component=Mozbase&product=Testing and https://bugzilla.mozilla.org/buglist.cgi?resolution=---&status_whiteboard_type=allwordssubstr&query_format=advanced&status_whiteboard=mozbase
+
+To file a bug, go to https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=Mozbase
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/README.txt
@@ -0,0 +1,345 @@
+ManifestDestiny
+===============
+
+Universal manifests for Mozilla test harnesses
+
+
+What is ManifestDestiny?
+------------------------
+
+What ManifestDestiny gives you::
+
+* manifests are (ordered) lists of tests
+* tests may have an arbitrary number of key, value pairs
+* the parser returns an ordered list of test data structures, which
+ are just dicts with some keys. For example, a test with no
+ user-specified metadata looks like this::
+
+ [{'path':
+ '/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests/testToolbar/testBackForwardButtons.js',
+ 'name': 'testToolbar/testBackForwardButtons.js', 'here':
+ '/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests',
+ 'manifest': '/home/jhammel/mozmill/src/ManifestDestiny/manifestdestiny/tests',}]
+
+The keys displayed here (path, name, here, and manifest) are reserved
+keys for ManifestDestiny and any consuming APIs. You can add
+additional key, value metadata to each test.
+
+
+Why have test manifests?
+------------------------
+
+Most Mozilla test harnesses work by crawling a directory structure.
+While this is straight-forward, manifests offer several practical
+advantages::
+
+* ability to turn a test off easily: if a test is broken on m-c
+ currently, the only way to turn it off, generally speaking, is just
+ removing the test. Often this is undesirable, as if the test should
+ be dismissed because other people want to land and it can't be
+ investigated in real time (is it a failure? is the test bad? is no
+ one around that knows the test?), then backing out a test is at best
+ problematic. With a manifest, a test may be disabled without
+ removing it from the tree and a bug filed with the appropriate
+ reason::
+
+ [test_broken.js]
+ disabled = https://bugzilla.mozilla.org/show_bug.cgi?id=123456
+
+* ability to run different (subsets of) tests on different
+ platforms. Traditionally, we've done a bit of magic or had the test
+ know what platform it would or would not run on. With manifests, you
+ can mark what platforms a test will or will not run on and change
+ these without changing the test.
+
+ [test_works_on_windows_only.js]
+ run-if = os == 'win'
+
+* ability to markup tests with metadata. We have a large, complicated,
+ and always changing infrastructure. key, value metadata may be used
+ as an annotation to a test and appropriately curated and mined. For
+ instance, we could mark certain tests as randomorange with a bug
+ number, if it were desirable.
+
+* ability to have sane and well-defined test-runs. You can keep
+ different manifests for different test runs and ``[include:]``
+ (sub)manifests as appropriate to your needs.
+
+
+Manifest Format
+---------------
+
+Manifests are .ini file with the section names denoting the path
+relative to the manifest::
+
+ [foo.js]
+ [bar.js]
+ [fleem.js]
+
+The sections are read in order. In addition, tests may include
+arbitrary key, value metadata to be used by the harness. You may also
+have a ``[DEFAULT]`` section that will give key, value pairs that will
+be inherited by each test unless overridden::
+
+ [DEFAULT]
+ type = restart
+
+ [lilies.js]
+ color = white
+
+ [daffodils.js]
+ color = yellow
+ type = other
+ # override type from DEFAULT
+
+ [roses.js]
+ color = red
+
+You can also include other manifests::
+
+ [include:subdir/anothermanifest.ini]
+
+Manifests are included relative to the directory of the manifest with
+the ``[include:]`` directive unless they are absolute paths.
+
+
+Data
+----
+
+Manifest Destiny gives tests as a list of dictionaries (in python
+terms).
+
+* path: full path to the test
+* name: short name of the test; this is the (usually) relative path
+ specified in the section name
+* here: the parent directory of the manifest
+* manifest: the path to the manifest containing the test
+
+This data corresponds to a one-line manifest::
+
+ [testToolbar/testBackForwardButtons.js]
+
+If additional key, values were specified, they would be in this dict
+as well.
+
+Outside of the reserved keys, the remaining key, values
+are up to convention to use. There is a (currently very minimal)
+generic integration layer in ManifestDestiny for use of all harnesses,
+``manifestparser.TestManifest``.
+For instance, if the 'disabled' key is present, you can get the set of
+tests without disabled (various other queries are doable as well).
+
+Since the system is convention-based, the harnesses may do whatever
+they want with the data. They may ignore it completely, they may use
+the provided integration layer, or they may provide their own
+integration layer. This should allow whatever sort of logic is
+desired. For instance, if in yourtestharness you wanted to run only on
+mondays for a certain class of tests::
+
+ tests = []
+ for test in manifests.tests:
+ if 'runOnDay' in test:
+ if calendar.day_name[calendar.weekday(*datetime.datetime.now().timetuple()[:3])].lower() == test['runOnDay'].lower():
+ tests.append(test)
+ else:
+ tests.append(test)
+
+To recap:
+* the manifests allow you to specify test data
+* the parser gives you this data
+* you can use it however you want or process it further as you need
+
+Tests are denoted by sections in an .ini file (see
+http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestdestiny/tests/mozmill-example.ini).
+
+Additional manifest files may be included with an ``[include:]`` directive::
+
+ [include:path-to-additional-file.manifest]
+
+The path to included files is relative to the current manifest.
+
+The ``[DEFAULT]`` section contains variables that all tests inherit from.
+
+Included files will inherit the top-level variables but may override
+in their own ``[DEFAULT]`` section.
+
+
+ManifestDestiny Architecture
+----------------------------
+
+There is a two- or three-layered approach to the ManifestDestiny
+architecture, depending on your needs::
+
+1. ManifestParser: this is a generic parser for .ini manifests that
+facilitates the `[include:]` logic and the inheritence of
+metadata. Despite the internal variable being called ``self.tests``
+(an oversight), this layer has nothing in particular to do with tests.
+
+2. TestManifest: this is a harness-agnostic integration layer that is
+test-specific. TestManifest faciliates ``skip-if`` and ``run-if``
+logic.
+
+3. Optionally, a harness will have an integration layer than inherits
+from TestManifest if more harness-specific customization is desired at
+the manifest level.
+
+See the source code at http://hg.mozilla.org/automation/ManifestDestiny
+and
+http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestparser.py
+in particular.
+
+
+Using Manifests
+---------------
+
+A test harness will normally call ``TestManifest.active_tests`` (
+http://hg.mozilla.org/automation/ManifestDestiny/file/c0399fbfa830/manifestparser.py#l506 )::
+
+ 506 def active_tests(self, exists=True, disabled=True, **tags):
+
+The manifests are passed to the ``__init__`` or ``read`` methods with
+appropriate arguments. ``active_tests`` then allows you to select the
+tests you want::
+
+- exists : return only existing tests
+- disabled : whether to return disabled tests; if not these will be
+ filtered out; if True (the default), the ``disabled`` key of a
+ test's metadata will be present and will be set to the reason that a
+ test is disabled
+- tags : keys and values to filter on (e.g. ``os='linux'``)
+
+``active_tests`` looks for tests with ``skip-if.${TAG}`` or
+``run-if``. If the condition is or is not fulfilled,
+respectively, the test is marked as disabled. For instance, if you
+pass ``**dict(os='linux')`` as ``**tags``, if a test contains a line
+``skip-if = os == 'linux'`` this test will be disabled, or
+``run-if = os = 'win'`` in which case the test will also be disabled. It
+is up to the harness to pass in tags appropriate to its usage.
+
+
+Creating Manifests
+------------------
+
+ManifestDestiny comes with a console script, ``manifestparser create``, that
+may be used to create a seed manifest structure from a directory of
+files. Run ``manifestparser help create`` for usage information.
+
+
+Copying Manifests
+-----------------
+
+To copy tests and manifests from a source::
+
+ manifestparser [options] copy from_manifest to_directory -tag1 -tag2 --key1=value1 key2=value2 ...
+
+
+Upating Tests
+-------------
+
+To update the tests associated with with a manifest from a source
+directory::
+
+ manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
+
+
+Tests
+-----
+
+ManifestDestiny includes a suite of tests:
+
+http://hg.mozilla.org/automation/ManifestDestiny/file/tip/manifestdestiny/tests
+
+``test_manifest.txt`` is a doctest that may be helpful in figuring out
+how to use the API. Tests are run via ``python test.py``.
+
+
+Bugs
+----
+
+Please file any bugs or feature requests at
+
+https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=ManifestParser
+
+Or contact jhammel @mozilla.org or in #ateam on irc.mozilla.org
+
+
+CLI
+---
+
+Run ``manifestparser help`` for usage information.
+
+To create a manifest from a set of directories::
+
+ manifestparser [options] create directory <directory> <...> [create-options]
+
+To output a manifest of tests::
+
+ manifestparser [options] write manifest <manifest> <...> -tag1 -tag2 --key1=value1 --key2=value2 ...
+
+To copy tests and manifests from a source::
+
+ manifestparser [options] copy from_manifest to_manifest -tag1 -tag2 --key1=value1 key2=value2 ...
+
+To update the tests associated with with a manifest from a source
+directory::
+
+ manifestparser [options] update manifest from_directory -tag1 -tag2 --key1=value1 --key2=value2 ...
+
+
+Design Considerations
+---------------------
+
+Contrary to some opinion, manifestparser.py and the associated .ini
+format were not magically plucked from the sky but were descended upon
+through several design considerations.
+
+* test manifests should be ordered. While python 2.6 and greater has
+ a ConfigParser that can use an ordered dictionary, it is a
+ requirement that we support python 2.4 for the build + testing
+ environment. To that end, a ``read_ini`` function was implemented
+ in manifestparser.py that should be the equivalent of the .ini
+ dialect used by ConfigParser.
+
+* the manifest format should be easily human readable/writable. While
+ there was initially some thought of using JSON, there was pushback
+ that JSON was not easily editable. An ideal manifest format would
+ degenerate to a line-separated list of files. While .ini format
+ requires an additional ``[]`` per line, and while there have been
+ complaints about this, hopefully this is good enough.
+
+* python does not have an in-built YAML parser. Since it was
+ undesirable for manifestparser.py to have any dependencies, YAML was
+ dismissed as a format.
+
+* we could have used a proprietary format but decided against it.
+ Everyone knows .ini and there are good tools to deal with it.
+ However, since read_ini is the only function that transforms a
+ manifest to a list of key, value pairs, while the implications for
+ changing the format impacts downstream code, doing so should be
+ programmatically simple.
+
+* there should be a single file that may easily be
+ transported. Traditionally, test harnesses have lived in
+ mozilla-central. This is less true these days and it is increasingly
+ likely that more tests will not live in mozilla-central going
+ forward. So ``manifestparser.py`` should be highly consumable. To
+ this end, it is a single file, as appropriate to mozilla-central,
+ which is also a working python package deployed to PyPI for easy
+ installation.
+
+
+Historical Reference
+--------------------
+
+Date-ordered list of links about how manifests came to be where they are today::
+
+* https://wiki.mozilla.org/Auto-tools/Projects/UniversalManifest
+* http://alice.nodelman.net/blog/post/2010/05/
+* http://alice.nodelman.net/blog/post/universal-manifest-for-unit-tests-a-proposal/
+* https://elvis314.wordpress.com/2010/07/05/improving-personal-hygiene-by-adjusting-mochitests/
+* https://elvis314.wordpress.com/2010/07/27/types-of-data-we-care-about-in-a-manifest/
+* https://bugzilla.mozilla.org/show_bug.cgi?id=585106
+* http://elvis314.wordpress.com/2011/05/20/converting-xpcshell-from-listing-directories-to-a-manifest/
+* https://bugzilla.mozilla.org/show_bug.cgi?id=616999
+* https://wiki.mozilla.org/Auto-tools/Projects/ManifestDestiny
+* https://developer.mozilla.org/en/Writing_xpcshell-based_unit_tests#Adding_your_tests_to_the_xpcshell_manifest
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/manifestparser.py
@@ -0,0 +1,1114 @@
+#!/usr/bin/env python
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is manifestdestiny.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Jeff Hammel <jhammel@mozilla.com> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+"""
+Mozilla universal manifest parser
+"""
+
+# this file lives at
+# http://hg.mozilla.org/automation/ManifestDestiny/raw-file/tip/manifestparser.py
+
+__all__ = ['read_ini', # .ini reader
+ 'ManifestParser', 'TestManifest', 'convert', # manifest handling
+ 'parse', 'ParseError', 'ExpressionParser'] # conditional expression parser
+
+import os
+import re
+import shutil
+import sys
+from fnmatch import fnmatch
+from optparse import OptionParser
+
+version = '0.5.4' # package version
+try:
+ from setuptools import setup
+except:
+ setup = None
+
+# we need relpath, but it is introduced in python 2.6
+# http://docs.python.org/library/os.path.html
+try:
+ relpath = os.path.relpath
+except AttributeError:
+ def relpath(path, start):
+ """
+ Return a relative version of a path
+ from /usr/lib/python2.6/posixpath.py
+ """
+
+ if not path:
+ raise ValueError("no path specified")
+
+ start_list = os.path.abspath(start).split(os.path.sep)
+ path_list = os.path.abspath(path).split(os.path.sep)
+
+ # Work out how much of the filepath is shared by start and path.
+ i = len(os.path.commonprefix([start_list, path_list]))
+
+ rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
+ if not rel_list:
+ return start
+ return os.path.join(*rel_list)
+
+# expr.py
+# from:
+# http://k0s.org/mozilla/hg/expressionparser
+# http://hg.mozilla.org/users/tmielczarek_mozilla.com/expressionparser
+
+# Implements a top-down parser/evaluator for simple boolean expressions.
+# ideas taken from http://effbot.org/zone/simple-top-down-parsing.htm
+#
+# Rough grammar:
+# expr := literal
+# | '(' expr ')'
+# | expr '&&' expr
+# | expr '||' expr
+# | expr '==' expr
+# | expr '!=' expr
+# literal := BOOL
+# | INT
+# | STRING
+# | IDENT
+# BOOL := true|false
+# INT := [0-9]+
+# STRING := "[^"]*"
+# IDENT := [A-Za-z_]\w*
+
+# Identifiers take their values from a mapping dictionary passed as the second
+# argument.
+
+# Glossary (see above URL for details):
+# - nud: null denotation
+# - led: left detonation
+# - lbp: left binding power
+# - rbp: right binding power
+
+class ident_token(object):
+ def __init__(self, value):
+ self.value = value
+ def nud(self, parser):
+ # identifiers take their value from the value mappings passed
+ # to the parser
+ return parser.value(self.value)
+
+class literal_token(object):
+ def __init__(self, value):
+ self.value = value
+ def nud(self, parser):
+ return self.value
+
+class eq_op_token(object):
+ "=="
+ def led(self, parser, left):
+ return left == parser.expression(self.lbp)
+
+class neq_op_token(object):
+ "!="
+ def led(self, parser, left):
+ return left != parser.expression(self.lbp)
+
+class not_op_token(object):
+ "!"
+ def nud(self, parser):
+ return not parser.expression()
+
+class and_op_token(object):
+ "&&"
+ def led(self, parser, left):
+ right = parser.expression(self.lbp)
+ return left and right
+
+class or_op_token(object):
+ "||"
+ def led(self, parser, left):
+ right = parser.expression(self.lbp)
+ return left or right
+
+class lparen_token(object):
+ "("
+ def nud(self, parser):
+ expr = parser.expression()
+ parser.advance(rparen_token)
+ return expr
+
+class rparen_token(object):
+ ")"
+
+class end_token(object):
+ """always ends parsing"""
+
+### derived literal tokens
+
+class bool_token(literal_token):
+ def __init__(self, value):
+ value = {'true':True, 'false':False}[value]
+ literal_token.__init__(self, value)
+
+class int_token(literal_token):
+ def __init__(self, value):
+ literal_token.__init__(self, int(value))
+
+class string_token(literal_token):
+ def __init__(self, value):
+ literal_token.__init__(self, value[1:-1])
+
+precedence = [(end_token, rparen_token),
+ (or_op_token,),
+ (and_op_token,),
+ (eq_op_token, neq_op_token),
+ (lparen_token,),
+ ]
+for index, rank in enumerate(precedence):
+ for token in rank:
+ token.lbp = index # lbp = lowest left binding power
+
+class ParseError(Exception):
+ """errror parsing conditional expression"""
+
+class ExpressionParser(object):
+ def __init__(self, text, valuemapping, strict=False):
+ """
+ Initialize the parser with input |text|, and |valuemapping| as
+ a dict mapping identifier names to values.
+ """
+ self.text = text
+ self.valuemapping = valuemapping
+ self.strict = strict
+
+ def _tokenize(self):
+ """
+ Lex the input text into tokens and yield them in sequence.
+ """
+ # scanner callbacks
+ def bool_(scanner, t): return bool_token(t)
+ def identifier(scanner, t): return ident_token(t)
+ def integer(scanner, t): return int_token(t)
+ def eq(scanner, t): return eq_op_token()
+ def neq(scanner, t): return neq_op_token()
+ def or_(scanner, t): return or_op_token()
+ def and_(scanner, t): return and_op_token()
+ def lparen(scanner, t): return lparen_token()
+ def rparen(scanner, t): return rparen_token()
+ def string_(scanner, t): return string_token(t)
+ def not_(scanner, t): return not_op_token()
+
+ scanner = re.Scanner([
+ (r"true|false", bool_),
+ (r"[a-zA-Z_]\w*", identifier),
+ (r"[0-9]+", integer),
+ (r'("[^"]*")|(\'[^\']*\')', string_),
+ (r"==", eq),
+ (r"!=", neq),
+ (r"\|\|", or_),
+ (r"!", not_),
+ (r"&&", and_),
+ (r"\(", lparen),
+ (r"\)", rparen),
+ (r"\s+", None), # skip whitespace
+ ])
+ tokens, remainder = scanner.scan(self.text)
+ for t in tokens:
+ yield t
+ yield end_token()
+
+ def value(self, ident):
+ """
+ Look up the value of |ident| in the value mapping passed in the
+ constructor.
+ """
+ if self.strict:
+ return self.valuemapping[ident]
+ else:
+ return self.valuemapping.get(ident, None)
+
+ def advance(self, expected):
+ """
+ Assert that the next token is an instance of |expected|, and advance
+ to the next token.
+ """
+ if not isinstance(self.token, expected):
+ raise Exception, "Unexpected token!"
+ self.token = self.iter.next()
+
+ def expression(self, rbp=0):
+ """
+ Parse and return the value of an expression until a token with
+ right binding power greater than rbp is encountered.
+ """
+ t = self.token
+ self.token = self.iter.next()
+ left = t.nud(self)
+ while rbp < self.token.lbp:
+ t = self.token
+ self.token = self.iter.next()
+ left = t.led(self, left)
+ return left
+
+ def parse(self):
+ """
+ Parse and return the value of the expression in the text
+ passed to the constructor. Raises a ParseError if the expression
+ could not be parsed.
+ """
+ try:
+ self.iter = self._tokenize()
+ self.token = self.iter.next()
+ return self.expression()
+ except:
+ raise ParseError("could not parse: %s; variables: %s" % (self.text, self.valuemapping))
+
+ __call__ = parse
+
+def parse(text, **values):
+ """
+ Parse and evaluate a boolean expression in |text|. Use |values| to look
+ up the value of identifiers referenced in the expression. Returns the final
+ value of the expression. A ParseError will be raised if parsing fails.
+ """
+ return ExpressionParser(text, values).parse()
+
+def normalize_path(path):
+ """normalize a relative path"""
+ if sys.platform.startswith('win'):
+ return path.replace('/', os.path.sep)
+ return path
+
+def denormalize_path(path):
+ """denormalize a relative path"""
+ if sys.platform.startswith('win'):
+ return path.replace(os.path.sep, '/')
+ return path
+
+
+def read_ini(fp, variables=None, default='DEFAULT',
+ comments=';#', separators=('=', ':'),
+ strict=True):
+ """
+ read an .ini file and return a list of [(section, values)]
+ - fp : file pointer or path to read
+ - variables : default set of variables
+ - default : name of the section for the default section
+ - comments : characters that if they start a line denote a comment
+ - separators : strings that denote key, value separation in order
+ - strict : whether to be strict about parsing
+ """
+
+ if variables is None:
+ variables = {}
+
+ if isinstance(fp, basestring):
+ fp = file(fp)
+
+ sections = []
+ key = value = None
+ section_names = set([])
+
+ # read the lines
+ for line in fp.readlines():
+
+ stripped = line.strip()
+
+ # ignore blank lines
+ if not stripped:
+ # reset key and value to avoid continuation lines
+ key = value = None
+ continue
+
+ # ignore comment lines
+ if stripped[0] in comments:
+ continue
+
+ # check for a new section
+ if len(stripped) > 2 and stripped[0] == '[' and stripped[-1] == ']':
+ section = stripped[1:-1].strip()
+ key = value = None
+
+ # deal with DEFAULT section
+ if section.lower() == default.lower():
+ if strict:
+ assert default not in section_names
+ section_names.add(default)
+ current_section = variables
+ continue
+
+ if strict:
+ # make sure this section doesn't already exist
+ assert section not in section_names
+
+ section_names.add(section)
+ current_section = {}
+ sections.append((section, current_section))
+ continue
+
+ # if there aren't any sections yet, something bad happen
+ if not section_names:
+ raise Exception('No sections found')
+
+ # (key, value) pair
+ for separator in separators:
+ if separator in stripped:
+ key, value = stripped.split(separator, 1)
+ key = key.strip()
+ value = value.strip()
+
+ if strict:
+ # make sure this key isn't already in the section or empty
+ assert key
+ if current_section is not variables:
+ assert key not in current_section
+
+ current_section[key] = value
+ break
+ else:
+ # continuation line ?
+ if line[0].isspace() and key:
+ value = '%s%s%s' % (value, os.linesep, stripped)
+ current_section[key] = value
+ else:
+ # something bad happen!
+ raise Exception("Not sure what you're trying to do")
+
+ # interpret the variables
+ def interpret_variables(global_dict, local_dict):
+ variables = global_dict.copy()
+ variables.update(local_dict)
+ return variables
+
+ sections = [(i, interpret_variables(variables, j)) for i, j in sections]
+ return sections
+
+
+### objects for parsing manifests
+
+class ManifestParser(object):
+ """read .ini manifests"""
+
+ ### methods for reading manifests
+
+ def __init__(self, manifests=(), defaults=None, strict=True):
+ self._defaults = defaults or {}
+ self.tests = []
+ self.strict = strict
+ self.rootdir = None
+ self.relativeRoot = None
+ if manifests:
+ self.read(*manifests)
+
+ def getRelativeRoot(self, root):
+ return root
+
+ def read(self, *filenames, **defaults):
+
+ # ensure all files exist
+ missing = [ filename for filename in filenames
+ if not os.path.exists(filename) ]
+ if missing:
+ raise IOError('Missing files: %s' % ', '.join(missing))
+
+ # process each file
+ for filename in filenames:
+
+ # set the per file defaults
+ defaults = defaults.copy() or self._defaults.copy()
+ here = os.path.dirname(os.path.abspath(filename))
+ defaults['here'] = here
+
+ if self.rootdir is None:
+ # set the root directory
+ # == the directory of the first manifest given
+ self.rootdir = here
+
+ # read the configuration
+ sections = read_ini(fp=filename, variables=defaults, strict=self.strict)
+
+ # get the tests
+ for section, data in sections:
+
+ # a file to include
+ # TODO: keep track of included file structure:
+ # self.manifests = {'manifest.ini': 'relative/path.ini'}
+ if section.startswith('include:'):
+ include_file = section.split('include:', 1)[-1]
+ include_file = normalize_path(include_file)
+ if not os.path.isabs(include_file):
+ include_file = os.path.join(self.getRelativeRoot(here), include_file)
+ if not os.path.exists(include_file):
+ if self.strict:
+ raise IOError("File '%s' does not exist" % include_file)
+ else:
+ continue
+ include_defaults = data.copy()
+ self.read(include_file, **include_defaults)
+ continue
+
+ # otherwise an item
+ test = data
+ test['name'] = section
+ test['manifest'] = os.path.abspath(filename)
+
+ # determine the path
+ path = test.get('path', section)
+ if '://' not in path: # don't futz with URLs
+ path = normalize_path(path)
+ if not os.path.isabs(path):
+ path = os.path.join(here, path)
+ test['path'] = path
+
+ # append the item
+ self.tests.append(test)
+
+ ### methods for querying manifests
+
+ def query(self, *checks, **kw):
+ """
+ general query function for tests
+ - checks : callable conditions to test if the test fulfills the query
+ """
+ tests = kw.get('tests', None)
+ if tests is None:
+ tests = self.tests
+ retval = []
+ for test in tests:
+ for check in checks:
+ if not check(test):
+ break
+ else:
+ retval.append(test)
+ return retval
+
+ def get(self, _key=None, inverse=False, tags=None, tests=None, **kwargs):
+ # TODO: pass a dict instead of kwargs since you might hav
+ # e.g. 'inverse' as a key in the dict
+
+ # TODO: tags should just be part of kwargs with None values
+ # (None == any is kinda weird, but probably still better)
+
+ # fix up tags
+ if tags:
+ tags = set(tags)
+ else:
+ tags = set()
+
+ # make some check functions
+ if inverse:
+ has_tags = lambda test: not tags.intersection(test.keys())
+ def dict_query(test):
+ for key, value in kwargs.items():
+ if test.get(key) == value:
+ return False
+ return True
+ else:
+ has_tags = lambda test: tags.issubset(test.keys())
+ def dict_query(test):
+ for key, value in kwargs.items():
+ if test.get(key) != value:
+ return False
+ return True
+
+ # query the tests
+ tests = self.query(has_tags, dict_query, tests=tests)
+
+ # if a key is given, return only a list of that key
+ # useful for keys like 'name' or 'path'
+ if _key:
+ return [test[_key] for test in tests]
+
+ # return the tests
+ return tests
+
+ def missing(self, tests=None):
+ """return list of tests that do not exist on the filesystem"""
+ if tests is None:
+ tests = self.tests
+ return [test for test in tests
+ if not os.path.exists(test['path'])]
+
+ def manifests(self, tests=None):
+ """
+ return manifests in order in which they appear in the tests
+ """
+ if tests is None:
+ tests = self.tests
+ manifests = []
+ for test in tests:
+ manifest = test.get('manifest')
+ if not manifest:
+ continue
+ if manifest not in manifests:
+ manifests.append(manifest)
+ return manifests
+
+ ### methods for outputting from manifests
+
+ def write(self, fp=sys.stdout, rootdir=None,
+ global_tags=None, global_kwargs=None,
+ local_tags=None, local_kwargs=None):
+ """
+ write a manifest given a query
+ global and local options will be munged to do the query
+ globals will be written to the top of the file
+ locals (if given) will be written per test
+ """
+
+ # root directory
+ if rootdir is None:
+ rootdir = self.rootdir
+
+ # sanitize input
+ global_tags = global_tags or set()
+ local_tags = local_tags or set()
+ global_kwargs = global_kwargs or {}
+ local_kwargs = local_kwargs or {}
+
+ # create the query
+ tags = set([])
+ tags.update(global_tags)
+ tags.update(local_tags)
+ kwargs = {}
+ kwargs.update(global_kwargs)
+ kwargs.update(local_kwargs)
+
+ # get matching tests
+ tests = self.get(tags=tags, **kwargs)
+
+ # print the .ini manifest
+ if global_tags or global_kwargs:
+ print >> fp, '[DEFAULT]'
+ for tag in global_tags:
+ print >> fp, '%s =' % tag
+ for key, value in global_kwargs.items():
+ print >> fp, '%s = %s' % (key, value)
+ print >> fp
+
+ for test in tests:
+ test = test.copy() # don't overwrite
+
+ path = test['name']
+ if not os.path.isabs(path):
+ path = test['path']
+ if self.rootdir:
+ path = relpath(test['path'], self.rootdir)
+ path = denormalize_path(path)
+ print >> fp, '[%s]' % path
+
+ # reserved keywords:
+ reserved = ['path', 'name', 'here', 'manifest']
+ for key in sorted(test.keys()):
+ if key in reserved:
+ continue
+ if key in global_kwargs:
+ continue
+ if key in global_tags and not test[key]:
+ continue
+ print >> fp, '%s = %s' % (key, test[key])
+ print >> fp
+
+ def copy(self, directory, rootdir=None, *tags, **kwargs):
+ """
+ copy the manifests and associated tests
+ - directory : directory to copy to
+ - rootdir : root directory to copy to (if not given from manifests)
+ - tags : keywords the tests must have
+ - kwargs : key, values the tests must match
+ """
+ # XXX note that copy does *not* filter the tests out of the
+ # resulting manifest; it just stupidly copies them over.
+ # ideally, it would reread the manifests and filter out the
+ # tests that don't match *tags and **kwargs
+
+ # destination
+ if not os.path.exists(directory):
+ os.path.makedirs(directory)
+ else:
+ # sanity check
+ assert os.path.isdir(directory)
+
+ # tests to copy
+ tests = self.get(tags=tags, **kwargs)
+ if not tests:
+ return # nothing to do!
+
+ # root directory
+ if rootdir is None:
+ rootdir = self.rootdir
+
+ # copy the manifests + tests
+ manifests = [relpath(manifest, rootdir) for manifest in self.manifests()]
+ for manifest in manifests:
+ destination = os.path.join(directory, manifest)
+ dirname = os.path.dirname(destination)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ else:
+ # sanity check
+ assert os.path.isdir(dirname)
+ shutil.copy(os.path.join(rootdir, manifest), destination)
+ for test in tests:
+ if os.path.isabs(test['name']):
+ continue
+ source = test['path']
+ if not os.path.exists(source):
+ print >> sys.stderr, "Missing test: '%s' does not exist!" % source
+ continue
+ # TODO: should err on strict
+ destination = os.path.join(directory, relpath(test['path'], rootdir))
+ shutil.copy(source, destination)
+ # TODO: ensure that all of the tests are below the from_dir
+
+ def update(self, from_dir, rootdir=None, *tags, **kwargs):
+ """
+ update the tests as listed in a manifest from a directory
+ - from_dir : directory where the tests live
+ - rootdir : root directory to copy to (if not given from manifests)
+ - tags : keys the tests must have
+ - kwargs : key, values the tests must match
+ """
+
+ # get the tests
+ tests = self.get(tags=tags, **kwargs)
+
+ # get the root directory
+ if not rootdir:
+ rootdir = self.rootdir
+
+ # copy them!
+ for test in tests:
+ if not os.path.isabs(test['name']):
+ _relpath = relpath(test['path'], rootdir)
+ source = os.path.join(from_dir, _relpath)
+ if not os.path.exists(source):
+ # TODO err on strict
+ print >> sys.stderr, "Missing test: '%s'; skipping" % test['name']
+ continue
+ destination = os.path.join(rootdir, _relpath)
+ shutil.copy(source, destination)
+
+
+class TestManifest(ManifestParser):
+ """
+ apply logic to manifests; this is your integration layer :)
+ specific harnesses may subclass from this if they need more logic
+ """
+
+ def filter(self, values, tests):
+ """
+ filter on a specific list tag, e.g.:
+ run-if.os = win linux
+ skip-if.os = mac
+ """
+
+ # tags:
+ run_tag = 'run-if'
+ skip_tag = 'skip-if'
+ fail_tag = 'fail-if'
+
+ # loop over test
+ for test in tests:
+ reason = None # reason to disable
+
+ # tagged-values to run
+ if run_tag in test:
+ condition = test[run_tag]
+ if not parse(condition, **values):
+ reason = '%s: %s' % (run_tag, condition)
+
+ # tagged-values to skip
+ if skip_tag in test:
+ condition = test[skip_tag]
+ if parse(condition, **values):
+ reason = '%s: %s' % (skip_tag, condition)
+
+ # mark test as disabled if there's a reason
+ if reason:
+ test.setdefault('disabled', reason)
+
+ # mark test as a fail if so indicated
+ if fail_tag in test:
+ condition = test[fail_tag]
+ if parse(condition, **values):
+ test['expected'] = 'fail'
+
+ def active_tests(self, exists=True, disabled=True, **values):
+ """
+ - exists : return only existing tests
+ - disabled : whether to return disabled tests
+ - tags : keys and values to filter on (e.g. `os = linux mac`)
+ """
+
+ tests = [i.copy() for i in self.tests] # shallow copy
+
+ # mark all tests as passing unless indicated otherwise
+ for test in tests:
+ test['expected'] = test.get('expected', 'pass')
+
+ # ignore tests that do not exist
+ if exists:
+ tests = [test for test in tests if os.path.exists(test['path'])]
+
+ # filter by tags
+ self.filter(values, tests)
+
+ # ignore disabled tests if specified
+ if not disabled:
+ tests = [test for test in tests
+ if not 'disabled' in test]
+
+ # return active tests
+ return tests
+
+ def test_paths(self):
+ return [test['path'] for test in self.active_tests()]
+
+
+### utility function(s); probably belongs elsewhere
+
+def convert(directories, pattern=None, ignore=(), write=None):
+ """
+ convert directories to a simple manifest
+ """
+
+ retval = []
+ include = []
+ for directory in directories:
+ for dirpath, dirnames, filenames in os.walk(directory):
+
+ # filter out directory names
+ dirnames = [ i for i in dirnames if i not in ignore ]
+ dirnames.sort()
+
+ # reference only the subdirectory
+ _dirpath = dirpath
+ dirpath = dirpath.split(directory, 1)[-1].strip(os.path.sep)
+
+ if dirpath.split(os.path.sep)[0] in ignore:
+ continue
+
+ # filter by glob
+ if pattern:
+ filenames = [filename for filename in filenames
+ if fnmatch(filename, pattern)]
+
+ filenames.sort()
+
+ # write a manifest for each directory
+ if write and (dirnames or filenames):
+ manifest = file(os.path.join(_dirpath, write), 'w')
+ for dirname in dirnames:
+ print >> manifest, '[include:%s]' % os.path.join(dirname, write)
+ for filename in filenames:
+ print >> manifest, '[%s]' % filename
+ manifest.close()
+
+ # add to the list
+ retval.extend([denormalize_path(os.path.join(dirpath, filename))
+ for filename in filenames])
+
+ if write:
+ return # the manifests have already been written!
+
+ retval.sort()
+ retval = ['[%s]' % filename for filename in retval]
+ return '\n'.join(retval)
+
+### command line attributes
+
+class ParserError(Exception):
+ """error for exceptions while parsing the command line"""
+
+def parse_args(_args):
+ """
+ parse and return:
+ --keys=value (or --key value)
+ -tags
+ args
+ """
+
+ # return values
+ _dict = {}
+ tags = []
+ args = []
+
+ # parse the arguments
+ key = None
+ for arg in _args:
+ if arg.startswith('---'):
+ raise ParserError("arguments should start with '-' or '--' only")
+ elif arg.startswith('--'):
+ if key:
+ raise ParserError("Key %s still open" % key)
+ key = arg[2:]
+ if '=' in key:
+ key, value = key.split('=', 1)
+ _dict[key] = value
+ key = None
+ continue
+ elif arg.startswith('-'):
+ if key:
+ raise ParserError("Key %s still open" % key)
+ tags.append(arg[1:])
+ continue
+ else:
+ if key:
+ _dict[key] = arg
+ continue
+ args.append(arg)
+
+ # return values
+ return (_dict, tags, args)
+
+
+### classes for subcommands
+
+class CLICommand(object):
+ usage = '%prog [options] command'
+ def __init__(self, parser):
+ self._parser = parser # master parser
+ def parser(self):
+ return OptionParser(usage=self.usage, description=self.__doc__,
+ add_help_option=False)
+
+class Copy(CLICommand):
+ usage = '%prog [options] copy manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
+ def __call__(self, options, args):
+ # parse the arguments
+ try:
+ kwargs, tags, args = parse_args(args)
+ except ParserError, e:
+ self._parser.error(e.message)
+
+ # make sure we have some manifests, otherwise it will
+ # be quite boring
+ if not len(args) == 2:
+ HelpCLI(self._parser)(options, ['copy'])
+ return
+
+ # read the manifests
+ # TODO: should probably ensure these exist here
+ manifests = ManifestParser()
+ manifests.read(args[0])
+
+ # print the resultant query
+ manifests.copy(args[1], None, *tags, **kwargs)
+
+
+class CreateCLI(CLICommand):
+ """
+ create a manifest from a list of directories
+ """
+ usage = '%prog [options] create directory <directory> <...>'
+
+ def parser(self):
+ parser = CLICommand.parser(self)
+ parser.add_option('-p', '--pattern', dest='pattern',
+ help="glob pattern for files")
+ parser.add_option('-i', '--ignore', dest='ignore',
+ default=[], action='append',
+ help='directories to ignore')
+ parser.add_option('-w', '--in-place', dest='in_place',
+ help='Write .ini files in place; filename to write to')
+ return parser
+
+ def __call__(self, _options, args):
+ parser = self.parser()
+ options, args = parser.parse_args(args)
+
+ # need some directories
+ if not len(args):
+ parser.print_usage()
+ return
+
+ # add the directories to the manifest
+ for arg in args:
+ assert os.path.exists(arg)
+ assert os.path.isdir(arg)
+ manifest = convert(args, pattern=options.pattern, ignore=options.ignore,
+ write=options.in_place)
+ if manifest:
+ print manifest
+
+
+class WriteCLI(CLICommand):
+ """
+ write a manifest based on a query
+ """
+ usage = '%prog [options] write manifest <manifest> -tag1 -tag2 --key1=value1 --key2=value2 ...'
+ def __call__(self, options, args):
+
+ # parse the arguments
+ try:
+ kwargs, tags, args = parse_args(args)
+ except ParserError, e:
+ self._parser.error(e.message)
+
+ # make sure we have some manifests, otherwise it will
+ # be quite boring
+ if not args:
+ HelpCLI(self._parser)(options, ['write'])
+ return
+
+ # read the manifests
+ # TODO: should probably ensure these exist here
+ manifests = ManifestParser()
+ manifests.read(*args)
+
+ # print the resultant query
+ manifests.write(global_tags=tags, global_kwargs=kwargs)
+
+
+class HelpCLI(CLICommand):
+ """
+ get help on a command
+ """
+ usage = '%prog [options] help [command]'
+
+ def __call__(self, options, args):
+ if len(args) == 1 and args[0] in commands:
+ commands[args[0]](self._parser).parser().print_help()
+ else:
+ self._parser.print_help()
+ print '\nCommands:'
+ for command in sorted(commands):
+ print ' %s : %s' % (command, commands[command].__doc__.strip())
+
+class SetupCLI(CLICommand):
+ """
+ setup using setuptools
+ """
+ # use setup.py from the repo when you want to distribute to python!
+ # otherwise setuptools will complain that it can't find setup.py
+ # and result in a useless package
+
+ usage = '%prog [options] setup [setuptools options]'
+
+ def __call__(self, options, args):
+ sys.argv = [sys.argv[0]] + args
+ assert setup is not None, "You must have setuptools installed to use SetupCLI"
+ here = os.path.dirname(os.path.abspath(__file__))
+ try:
+ filename = os.path.join(here, 'README.txt')
+ description = file(filename).read()
+ except:
+ description = ''
+ os.chdir(here)
+
+ setup(name='ManifestDestiny',
+ version=version,
+ description="Universal manifests for Mozilla test harnesses",
+ long_description=description,
+ classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ keywords='mozilla manifests',
+ author='Jeff Hammel',
+ author_email='jhammel@mozilla.com',
+ url='https://wiki.mozilla.org/Auto-tools/Projects/ManifestDestiny',
+ license='MPL',
+ zip_safe=False,
+ py_modules=['manifestparser'],
+ install_requires=[
+ # -*- Extra requirements: -*-
+ ],
+ entry_points="""
+ [console_scripts]
+ manifestparser = manifestparser:main
+ """,
+ )
+
+
+class UpdateCLI(CLICommand):
+ """
+ update the tests as listed in a manifest from a directory
+ """
+ usage = '%prog [options] update manifest directory -tag1 -tag2 --key1=value1 --key2=value2 ...'
+
+ def __call__(self, options, args):
+ # parse the arguments
+ try:
+ kwargs, tags, args = parse_args(args)
+ except ParserError, e:
+ self._parser.error(e.message)
+
+ # make sure we have some manifests, otherwise it will
+ # be quite boring
+ if not len(args) == 2:
+ HelpCLI(self._parser)(options, ['update'])
+ return
+
+ # read the manifests
+ # TODO: should probably ensure these exist here
+ manifests = ManifestParser()
+ manifests.read(args[0])
+
+ # print the resultant query
+ manifests.update(args[1], None, *tags, **kwargs)
+
+
+# command -> class mapping
+commands = { 'create': CreateCLI,
+ 'help': HelpCLI,
+ 'update': UpdateCLI,
+ 'write': WriteCLI }
+if setup is not None:
+ commands['setup'] = SetupCLI
+
+def main(args=sys.argv[1:]):
+ """console_script entry point"""
+
+ # set up an option parser
+ usage = '%prog [options] [command] ...'
+ description = __doc__
+ parser = OptionParser(usage=usage, description=description)
+ parser.add_option('-s', '--strict', dest='strict',
+ action='store_true', default=False,
+ help='adhere strictly to errors')
+ parser.disable_interspersed_args()
+
+ options, args = parser.parse_args(args)
+
+ if not args:
+ HelpCLI(parser)(options, args)
+ parser.exit()
+
+ # get the command
+ command = args[0]
+ if command not in commands:
+ parser.error("Command must be one of %s (you gave '%s')" % (', '.join(sorted(commands.keys())), command))
+
+ handler = commands[command](parser)
+ handler(options, args[1:])
+
+if __name__ == '__main__':
+ main()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/setup.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is manifestdestiny.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Jeff Hammel <jhammel@mozilla.com> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+# The real details are in manifestparser.py; this is just a front-end
+# BUT use this file when you want to distribute to python!
+# otherwise setuptools will complain that it can't find setup.py
+# and result in a useless package
+
+import sys
+from manifestparser import SetupCLI
+SetupCLI(None)(None, sys.argv[1:])
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/filter-example.ini
@@ -0,0 +1,11 @@
+# illustrate test filters based on various categories
+
+[windowstest]
+run-if = os == 'win'
+
+[fleem]
+skip-if = os == 'mac'
+
+[linuxtest]
+skip-if = (os == 'mac') || (os == 'win')
+fail-if = toolkit == 'cocoa'
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/fleem
@@ -0,0 +1,1 @@
+# dummy spot for "fleem" test
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/include-example.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+foo = bar
+
+[include:include/bar.ini]
+
+[fleem]
+
+[include:include/foo.ini]
+red = roses
+blue = violets
+yellow = daffodils
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/include/bar.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+foo = fleem
+
+[crash-handling]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/include/crash-handling
@@ -0,0 +1,1 @@
+# dummy spot for "crash-handling" test
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/include/flowers
@@ -0,0 +1,1 @@
+# dummy spot for "flowers" test
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/include/foo.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+blue = ocean
+
+[flowers]
+yellow = submarine
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/mozmill-example.ini
@@ -0,0 +1,80 @@
+[testAddons/testDisableEnablePlugin.js]
+[testAddons/testGetAddons.js]
+[testAddons/testSearchAddons.js]
+[testAwesomeBar/testAccessLocationBar.js]
+[testAwesomeBar/testCheckItemHighlight.js]
+[testAwesomeBar/testEscapeAutocomplete.js]
+[testAwesomeBar/testFaviconInAutocomplete.js]
+[testAwesomeBar/testGoButton.js]
+[testAwesomeBar/testLocationBarSearches.js]
+[testAwesomeBar/testPasteLocationBar.js]
+[testAwesomeBar/testSuggestHistoryBookmarks.js]
+[testAwesomeBar/testVisibleItemsMax.js]
+[testBookmarks/testAddBookmarkToMenu.js]
+[testCookies/testDisableCookies.js]
+[testCookies/testEnableCookies.js]
+[testCookies/testRemoveAllCookies.js]
+[testCookies/testRemoveCookie.js]
+[testDownloading/testCloseDownloadManager.js]
+[testDownloading/testDownloadStates.js]
+[testDownloading/testOpenDownloadManager.js]
+[testFindInPage/testFindInPage.js]
+[testFormManager/testAutoCompleteOff.js]
+[testFormManager/testBasicFormCompletion.js]
+[testFormManager/testClearFormHistory.js]
+[testFormManager/testDisableFormManager.js]
+[testGeneral/testGoogleSuggestions.js]
+[testGeneral/testStopReloadButtons.js]
+[testInstallation/testBreakpadInstalled.js]
+[testLayout/testNavigateFTP.js]
+[testPasswordManager/testPasswordNotSaved.js]
+[testPasswordManager/testPasswordSavedAndDeleted.js]
+[testPopups/testPopupsAllowed.js]
+[testPopups/testPopupsBlocked.js]
+[testPreferences/testPaneRetention.js]
+[testPreferences/testPreferredLanguage.js]
+[testPreferences/testRestoreHomepageToDefault.js]
+[testPreferences/testSetToCurrentPage.js]
+[testPreferences/testSwitchPanes.js]
+[testPrivateBrowsing/testAboutPrivateBrowsing.js]
+[testPrivateBrowsing/testCloseWindow.js]
+[testPrivateBrowsing/testDisabledElements.js]
+[testPrivateBrowsing/testDisabledPermissions.js]
+[testPrivateBrowsing/testDownloadManagerClosed.js]
+[testPrivateBrowsing/testGeolocation.js]
+[testPrivateBrowsing/testStartStopPBMode.js]
+[testPrivateBrowsing/testTabRestoration.js]
+[testPrivateBrowsing/testTabsDismissedOnStop.js]
+[testSearch/testAddMozSearchProvider.js]
+[testSearch/testFocusAndSearch.js]
+[testSearch/testGetMoreSearchEngines.js]
+[testSearch/testOpenSearchAutodiscovery.js]
+[testSearch/testRemoveSearchEngine.js]
+[testSearch/testReorderSearchEngines.js]
+[testSearch/testRestoreDefaults.js]
+[testSearch/testSearchSelection.js]
+[testSearch/testSearchSuggestions.js]
+[testSecurity/testBlueLarry.js]
+[testSecurity/testDefaultPhishingEnabled.js]
+[testSecurity/testDefaultSecurityPrefs.js]
+[testSecurity/testEncryptedPageWarning.js]
+[testSecurity/testGreenLarry.js]
+[testSecurity/testGreyLarry.js]
+[testSecurity/testIdentityPopupOpenClose.js]
+[testSecurity/testSSLDisabledErrorPage.js]
+[testSecurity/testSafeBrowsingNotificationBar.js]
+[testSecurity/testSafeBrowsingWarningPages.js]
+[testSecurity/testSecurityInfoViaMoreInformation.js]
+[testSecurity/testSecurityNotification.js]
+[testSecurity/testSubmitUnencryptedInfoWarning.js]
+[testSecurity/testUnknownIssuer.js]
+[testSecurity/testUntrustedConnectionErrorPage.js]
+[testSessionStore/testUndoTabFromContextMenu.js]
+[testTabbedBrowsing/testBackgroundTabScrolling.js]
+[testTabbedBrowsing/testCloseTab.js]
+[testTabbedBrowsing/testNewTab.js]
+[testTabbedBrowsing/testNewWindow.js]
+[testTabbedBrowsing/testOpenInBackground.js]
+[testTabbedBrowsing/testOpenInForeground.js]
+[testTechnicalTools/testAccessPageInfoDialog.js]
+[testToolbar/testBackForwardButtons.js]
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/mozmill-restart-example.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+type = restart
+
+[restartTests/testExtensionInstallUninstall/test2.js]
+foo = bar
+
+[restartTests/testExtensionInstallUninstall/test1.js]
+foo = baz
+
+[restartTests/testExtensionInstallUninstall/test3.js]
+[restartTests/testSoftwareUpdateAutoProxy/test2.js]
+[restartTests/testSoftwareUpdateAutoProxy/test1.js]
+[restartTests/testMasterPassword/test1.js]
+[restartTests/testExtensionInstallGetAddons/test2.js]
+[restartTests/testExtensionInstallGetAddons/test1.js]
+[restartTests/testMultipleExtensionInstallation/test2.js]
+[restartTests/testMultipleExtensionInstallation/test1.js]
+[restartTests/testThemeInstallUninstall/test2.js]
+[restartTests/testThemeInstallUninstall/test1.js]
+[restartTests/testThemeInstallUninstall/test3.js]
+[restartTests/testDefaultBookmarks/test1.js]
+[softwareUpdate/testFallbackUpdate/test2.js]
+[softwareUpdate/testFallbackUpdate/test1.js]
+[softwareUpdate/testFallbackUpdate/test3.js]
+[softwareUpdate/testDirectUpdate/test2.js]
+[softwareUpdate/testDirectUpdate/test1.js]
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/path-example.ini
@@ -0,0 +1,2 @@
+[foo]
+path = fleem
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/test.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla.org.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Jeff Hammel <jhammel@mozilla.com> (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+"""tests for ManifestDestiny"""
+
+import doctest
+import os
+import sys
+from optparse import OptionParser
+
+def run_tests(raise_on_error=False, report_first=False):
+
+ # add results here
+ results = {}
+
+ # doctest arguments
+ directory = os.path.dirname(os.path.abspath(__file__))
+ extraglobs = {}
+ doctest_args = dict(extraglobs=extraglobs,
+ module_relative=False,
+ raise_on_error=raise_on_error)
+ if report_first:
+ doctest_args['optionflags'] = doctest.REPORT_ONLY_FIRST_FAILURE
+
+ # gather tests
+ directory = os.path.dirname(os.path.abspath(__file__))
+ tests = [ test for test in os.listdir(directory)
+ if test.endswith('.txt') and test.startswith('test_')]
+ os.chdir(directory)
+
+ # run the tests
+ for test in tests:
+ try:
+ results[test] = doctest.testfile(test, **doctest_args)
+ except doctest.DocTestFailure, failure:
+ raise
+ except doctest.UnexpectedException, failure:
+ raise failure.exc_info[0], failure.exc_info[1], failure.exc_info[2]
+
+ return results
+
+
+def main(args=sys.argv[1:]):
+
+ # parse command line options
+ parser = OptionParser(description=__doc__)
+ parser.add_option('--raise', dest='raise_on_error',
+ default=False, action='store_true',
+ help="raise on first error")
+ parser.add_option('--report-first', dest='report_first',
+ default=False, action='store_true',
+ help="report the first error only (all tests will still run)")
+ parser.add_option('-q', '--quiet', dest='quiet',
+ default=False, action='store_true',
+ help="minimize output")
+ options, args = parser.parse_args(args)
+ quiet = options.__dict__.pop('quiet')
+
+ # run the tests
+ results = run_tests(**options.__dict__)
+
+ # check for failure
+ failed = False
+ for result in results.values():
+ if result[0]: # failure count; http://docs.python.org/library/doctest.html#basic-api
+ failed = True
+ break
+ if failed:
+ sys.exit(1) # error
+ if not quiet:
+ # print results
+ print "manifestparser.py: All tests pass!"
+ for test in sorted(results.keys()):
+ result = results[test]
+ print "%s: failed=%s, attempted=%s" % (test, result[0], result[1])
+
+if __name__ == '__main__':
+ main()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/test_expressionparser.txt
@@ -0,0 +1,120 @@
+Test Expressionparser
+=====================
+
+Test the conditional expression parser.
+
+Boilerplate::
+
+ >>> from manifestparser import parse
+
+Test basic values::
+
+ >>> parse("1")
+ 1
+ >>> parse("100")
+ 100
+ >>> parse("true")
+ True
+ >>> parse("false")
+ False
+ >>> '' == parse('""')
+ True
+ >>> parse('"foo bar"')
+ 'foo bar'
+ >>> parse("'foo bar'")
+ 'foo bar'
+ >>> parse("foo", foo=1)
+ 1
+ >>> parse("bar", bar=True)
+ True
+ >>> parse("abc123", abc123="xyz")
+ 'xyz'
+
+Test equality::
+
+ >>> parse("true == true")
+ True
+ >>> parse("false == false")
+ True
+ >>> parse("false == false")
+ True
+ >>> parse("1 == 1")
+ True
+ >>> parse("100 == 100")
+ True
+ >>> parse('"some text" == "some text"')
+ True
+ >>> parse("true != false")
+ True
+ >>> parse("1 != 2")
+ True
+ >>> parse('"text" != "other text"')
+ True
+ >>> parse("foo == true", foo=True)
+ True
+ >>> parse("foo == 1", foo=1)
+ True
+ >>> parse('foo == "bar"', foo='bar')
+ True
+ >>> parse("foo == bar", foo=True, bar=True)
+ True
+ >>> parse("true == foo", foo=True)
+ True
+ >>> parse("foo != true", foo=False)
+ True
+ >>> parse("foo != 2", foo=1)
+ True
+ >>> parse('foo != "bar"', foo='abc')
+ True
+ >>> parse("foo != bar", foo=True, bar=False)
+ True
+ >>> parse("true != foo", foo=False)
+ True
+ >>> parse("!false")
+ True
+
+Test conjunctions::
+
+ >>> parse("true && true")
+ True
+ >>> parse("true || false")
+ True
+ >>> parse("false || false")
+ False
+ >>> parse("true && false")
+ False
+ >>> parse("true || false && false")
+ True
+
+Test parentheses::
+
+ >>> parse("(true)")
+ True
+ >>> parse("(10)")
+ 10
+ >>> parse('("foo")')
+ 'foo'
+ >>> parse("(foo)", foo=1)
+ 1
+ >>> parse("(true == true)")
+ True
+ >>> parse("(true != false)")
+ True
+ >>> parse("(true && true)")
+ True
+ >>> parse("(true || false)")
+ True
+ >>> parse("(true && true || false)")
+ True
+ >>> parse("(true || false) && false")
+ False
+ >>> parse("(true || false) && true")
+ True
+ >>> parse("true && (true || false)")
+ True
+ >>> parse("true && (true || false)")
+ True
+ >>> parse("(true && false) || (true && (true || false))")
+ True
+
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/test_manifestparser.txt
@@ -0,0 +1,217 @@
+Test the manifest parser
+========================
+
+You must have ManifestDestiny installed before running these tests.
+Run ``python manifestparser.py setup develop`` with setuptools installed.
+
+Ensure basic parser is sane::
+
+ >>> from manifestparser import ManifestParser
+ >>> parser = ManifestParser()
+ >>> parser.read('mozmill-example.ini')
+ >>> tests = parser.tests
+ >>> len(tests) == len(file('mozmill-example.ini').read().strip().splitlines())
+ True
+
+Ensure that capitalization and order aren't an issue:
+
+ >>> lines = ['[%s]' % test['name'] for test in tests]
+ >>> lines == file('mozmill-example.ini').read().strip().splitlines()
+ True
+
+Show how you select subsets of tests:
+
+ >>> parser.read('mozmill-restart-example.ini')
+ >>> restart_tests = parser.get(type='restart')
+ >>> len(restart_tests) < len(parser.tests)
+ True
+ >>> import os
+ >>> len(restart_tests) == len(parser.get(manifest=os.path.abspath('mozmill-restart-example.ini')))
+ True
+ >>> assert not [test for test in restart_tests if test['manifest'] != os.path.abspath('mozmill-restart-example.ini')]
+ >>> parser.get('name', tags=['foo'])
+ ['restartTests/testExtensionInstallUninstall/test2.js', 'restartTests/testExtensionInstallUninstall/test1.js']
+ >>> parser.get('name', foo='bar')
+ ['restartTests/testExtensionInstallUninstall/test2.js']
+
+Illustrate how include works::
+
+ >>> parser = ManifestParser(manifests=('include-example.ini',))
+
+All of the tests should be included, in order::
+
+ >>> parser.get('name')
+ ['crash-handling', 'fleem', 'flowers']
+ >>> [(test['name'], os.path.basename(test['manifest'])) for test in parser.tests]
+ [('crash-handling', 'bar.ini'), ('fleem', 'include-example.ini'), ('flowers', 'foo.ini')]
+
+The manifests should be there too::
+
+ >>> len(parser.manifests())
+ 3
+
+We're already in the root directory::
+
+ >>> os.getcwd() == parser.rootdir
+ True
+
+DEFAULT values should persist across includes, unless they're
+overwritten. In this example, include-example.ini sets foo=bar, but
+its overridden to fleem in bar.ini::
+
+ >>> parser.get('name', foo='bar')
+ ['fleem', 'flowers']
+ >>> parser.get('name', foo='fleem')
+ ['crash-handling']
+
+Passing parameters in the include section allows defining variables in
+the submodule scope:
+
+ >>> parser.get('name', tags=['red'])
+ ['flowers']
+
+However, this should be overridable from the DEFAULT section in the
+included file and that overridable via the key directly connected to
+the test::
+
+ >>> parser.get(name='flowers')[0]['blue']
+ 'ocean'
+ >>> parser.get(name='flowers')[0]['yellow']
+ 'submarine'
+
+You can query multiple times if you need to::
+
+ >>> flowers = parser.get(foo='bar')
+ >>> len(flowers)
+ 2
+ >>> roses = parser.get(tests=flowers, red='roses')
+
+Using the inverse flag should invert the set of tests returned::
+
+ >>> parser.get('name', inverse=True, tags=['red'])
+ ['crash-handling', 'fleem']
+
+All of the included tests actually exist::
+
+ >>> [i['name'] for i in parser.missing()]
+ []
+
+Write the output to a manifest:
+
+ >>> from StringIO import StringIO
+ >>> buffer = StringIO()
+ >>> parser.write(fp=buffer, global_kwargs={'foo': 'bar'})
+ >>> buffer.getvalue().strip()
+ '[DEFAULT]\nfoo = bar\n\n[fleem]\n\n[include/flowers]\nblue = ocean\nred = roses\nyellow = submarine'
+
+Test our ability to convert a static directory structure to a
+manifest. First, stub out a directory with files in it::
+
+ >>> import shutil, tempfile
+ >>> def create_stub():
+ ... directory = tempfile.mkdtemp()
+ ... for i in 'foo', 'bar', 'fleem':
+ ... file(os.path.join(directory, i), 'w').write(i)
+ ... subdir = os.path.join(directory, 'subdir')
+ ... os.mkdir(subdir)
+ ... file(os.path.join(subdir, 'subfile'), 'w').write('baz')
+ ... return directory
+ >>> stub = create_stub()
+ >>> os.path.exists(stub) and os.path.isdir(stub)
+ True
+
+Make a manifest for it::
+
+ >>> from manifestparser import convert
+ >>> print convert([stub])
+ [bar]
+ [fleem]
+ [foo]
+ [subdir/subfile]
+ >>> shutil.rmtree(stub)
+
+Now do the same thing but keep the manifests in place::
+
+ >>> stub = create_stub()
+ >>> convert([stub], write='manifest.ini')
+ >>> sorted(os.listdir(stub))
+ ['bar', 'fleem', 'foo', 'manifest.ini', 'subdir']
+ >>> parser = ManifestParser()
+ >>> parser.read(os.path.join(stub, 'manifest.ini'))
+ >>> [i['name'] for i in parser.tests]
+ ['subfile', 'bar', 'fleem', 'foo']
+ >>> parser = ManifestParser()
+ >>> parser.read(os.path.join(stub, 'subdir', 'manifest.ini'))
+ >>> len(parser.tests)
+ 1
+ >>> parser.tests[0]['name']
+ 'subfile'
+ >>> shutil.rmtree(stub)
+
+Test our ability to copy a set of manifests::
+
+ >>> tempdir = tempfile.mkdtemp()
+ >>> manifest = ManifestParser(manifests=('include-example.ini',))
+ >>> manifest.copy(tempdir)
+ >>> sorted(os.listdir(tempdir))
+ ['fleem', 'include', 'include-example.ini']
+ >>> sorted(os.listdir(os.path.join(tempdir, 'include')))
+ ['bar.ini', 'crash-handling', 'flowers', 'foo.ini']
+ >>> from_manifest = ManifestParser(manifests=('include-example.ini',))
+ >>> to_manifest = os.path.join(tempdir, 'include-example.ini')
+ >>> to_manifest = ManifestParser(manifests=(to_manifest,))
+ >>> to_manifest.get('name') == from_manifest.get('name')
+ True
+ >>> shutil.rmtree(tempdir)
+
+Test our ability to update tests from a manifest and a directory of
+files::
+
+ >>> tempdir = tempfile.mkdtemp()
+ >>> for i in range(10):
+ ... file(os.path.join(tempdir, str(i)), 'w').write(str(i))
+
+First, make a manifest::
+
+ >>> manifest = convert([tempdir])
+ >>> newtempdir = tempfile.mkdtemp()
+ >>> manifest_file = os.path.join(newtempdir, 'manifest.ini')
+ >>> file(manifest_file,'w').write(manifest)
+ >>> manifest = ManifestParser(manifests=(manifest_file,))
+ >>> manifest.get('name') == [str(i) for i in range(10)]
+ True
+
+All of the tests are initially missing::
+
+ >>> [i['name'] for i in manifest.missing()] == [str(i) for i in range(10)]
+ True
+
+But then we copy one over::
+
+ >>> manifest.get('name', name='1')
+ ['1']
+ >>> manifest.update(tempdir, name='1')
+ >>> sorted(os.listdir(newtempdir))
+ ['1', 'manifest.ini']
+
+Update that one file and copy all the "tests"::
+
+ >>> file(os.path.join(tempdir, '1'), 'w').write('secret door')
+ >>> manifest.update(tempdir)
+ >>> sorted(os.listdir(newtempdir))
+ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'manifest.ini']
+ >>> file(os.path.join(newtempdir, '1')).read().strip()
+ 'secret door'
+
+Clean up::
+
+ >>> shutil.rmtree(tempdir)
+ >>> shutil.rmtree(newtempdir)
+
+You can override the path in the section too. This shows that you can
+use a relative path::
+
+ >>> manifest = ManifestParser(manifests=('path-example.ini',))
+ >>> manifest.tests[0]['path'] == os.path.abspath('fleem')
+ True
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/manifestdestiny/tests/test_testmanifest.txt
@@ -0,0 +1,32 @@
+Test the Test Manifest
+======================
+
+Boilerplate::
+
+ >>> import os
+
+Test filtering based on platform::
+
+ >>> from manifestparser import TestManifest
+ >>> manifest = TestManifest(manifests=('filter-example.ini',))
+ >>> [i['name'] for i in manifest.active_tests(os='win', disabled=False, exists=False)]
+ ['windowstest', 'fleem']
+ >>> [i['name'] for i in manifest.active_tests(os='linux', disabled=False, exists=False)]
+ ['fleem', 'linuxtest']
+
+Look for existing tests. There is only one::
+
+ >>> [i['name'] for i in manifest.active_tests()]
+ ['fleem']
+
+You should be able to expect failures::
+
+ >>> last_test = manifest.active_tests(exists=False, toolkit='gtk2')[-1]
+ >>> last_test['name']
+ 'linuxtest'
+ >>> last_test['expected']
+ 'pass'
+ >>> last_test = manifest.active_tests(exists=False, toolkit='cocoa')[-1]
+ >>> last_test['expected']
+ 'fail'
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozhttpd/README.md
@@ -0,0 +1,1 @@
+basic python webserver, tested with talos
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozhttpd/mozhttpd.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Joel Maher <joel.maher@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import BaseHTTPServer
+import SimpleHTTPServer
+import threading
+import sys
+import os
+import urllib
+import re
+from SocketServer import ThreadingMixIn
+
+class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
+ allow_reuse_address = True
+
+class MozRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ docroot = os.getcwd()
+
+ def parse_request(self):
+ retval = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
+ if '?' in self.path:
+ # ignore query string, otherwise SimpleHTTPRequestHandler
+ # will treat it as PATH_INFO for `translate_path`
+ self.path = self.path.split('?', 1)[0]
+ return retval
+
+ def translate_path(self, path):
+ path = path.strip('/').split()
+ if path == ['']:
+ path = []
+ path.insert(0, self.docroot)
+ return os.path.join(*path)
+
+ # I found on my local network that calls to this were timing out
+ # I believe all of these calls are from log_message
+ def address_string(self):
+ return "a.b.c.d"
+
+ # This produces a LOT of noise
+ def log_message(self, format, *args):
+ pass
+
+class MozHttpd(object):
+
+ def __init__(self, host="127.0.0.1", port=8888, docroot=os.getcwd()):
+ self.host = host
+ self.port = int(port)
+ self.docroot = docroot
+ self.httpd = None
+
+ def start(self, block=False):
+ """
+ start the server. If block is True, the call will not return.
+ If block is False, the server will be started on a separate thread that
+ can be terminated by a call to .stop()
+ """
+
+ class MozRequestHandlerInstance(MozRequestHandler):
+ docroot = self.docroot
+
+ self.httpd = EasyServer((self.host, self.port), MozRequestHandlerInstance)
+ if block:
+ self.httpd.serve_forever()
+ else:
+ self.server = threading.Thread(target=self.httpd.serve_forever)
+ self.server.setDaemon(True) # don't hang on exit
+ self.server.start()
+
+ def testServer(self):
+ fileList = os.listdir(self.docroot)
+ filehandle = urllib.urlopen('http://%s:%s/?foo=bar&fleem=&foo=fleem' % (self.host, self.port))
+ data = filehandle.readlines()
+ filehandle.close()
+
+ retval = True
+
+ for line in data:
+ found = False
+ # '@' denotes a symlink and we need to ignore it.
+ webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>', '', line.strip('\n')).strip('/').strip().strip('@')
+ if webline != "":
+ if webline == "Directory listing for":
+ found = True
+ else:
+ for fileName in fileList:
+ if fileName == webline:
+ found = True
+
+ if not found:
+ retval = False
+ print >> sys.stderr, "NOT FOUND: " + webline.strip()
+ return retval
+
+ def stop(self):
+ if self.httpd:
+ self.httpd.shutdown()
+ self.httpd = None
+
+ __del__ = stop
+
+
+def main(args=sys.argv[1:]):
+
+ # parse command line options
+ from optparse import OptionParser
+ parser = OptionParser()
+ parser.add_option('-p', '--port', dest='port',
+ type="int", default=8888,
+ help="port to run the server on [DEFAULT: %default]")
+ parser.add_option('-H', '--host', dest='host',
+ default='127.0.0.1',
+ help="host [DEFAULT: %default]")
+ parser.add_option('-d', '--docroot', dest='docroot',
+ default=os.getcwd(),
+ help="directory to serve files from [DEFAULT: %default]")
+ parser.add_option('--test', dest='test',
+ action='store_true', default=False,
+ help='run the tests and exit')
+ options, args = parser.parse_args(args)
+ if args:
+ parser.print_help()
+ parser.exit()
+
+ # create the server
+ kwargs = options.__dict__.copy()
+ test = kwargs.pop('test')
+ server = MozHttpd(**kwargs)
+
+ if test:
+ server.start()
+ server.testServer()
+ else:
+ server.start(block=True)
+
+if __name__ == '__main__':
+ main()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozhttpd/setup.py
@@ -0,0 +1,72 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozhttpd.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Joel Maher <jmaher@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import os
+from setuptools import setup
+
+try:
+ here = os.path.dirname(os.path.abspath(__file__))
+ description = file(os.path.join(here, 'README.md')).read()
+except IOError:
+ description = None
+
+version = '0.1'
+
+deps = []
+
+setup(name='mozhttpd',
+ version=version,
+ description="basic python webserver, tested with talos",
+ long_description=description,
+ classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ keywords='mozilla',
+ author='Joel Maher',
+ author_email='tools@lists.mozilla.org',
+ url='https://github.com/mozilla/mozbase/tree/master/mozhttpd',
+ license='MPL',
+ py_modules=['mozhttpd'],
+ packages=[],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=deps,
+ entry_points="""
+ # -*- Entry points: -*-
+ [console_scripts]
+ mozhttpd = mozhttpd:main
+ """,
+ )
+
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinfo/README.md
@@ -0,0 +1,62 @@
+Throughout [mozmill](https://developer.mozilla.org/en/Mozmill)
+and other Mozilla python code, checking the underlying
+platform is done in many different ways. The various checks needed
+lead to a lot of copy+pasting, leaving the reader to wonder....is this
+specific check necessary for (e.g.) an operating system? Because
+information is not consolidated, checks are not done consistently, nor
+is it defined what we are checking for.
+
+[MozInfo](https://github.com/mozilla/mozbase/tree/master/mozinfo)
+proposes to solve this problem. MozInfo is a bridge interface,
+making the underlying (complex) plethora of OS and architecture
+combinations conform to a subset of values of relavence to
+Mozilla software. The current implementation exposes relavent key,
+values: `os`, `version`, `bits`, and `processor`. Additionally, the
+service pack in use is available on the windows platform.
+
+
+# API Usage
+
+MozInfo is a python package. Downloading the software and running
+`python setup.py develop` will allow you to do `import mozinfo`
+from python.
+[mozinfo.py](https://github.com/mozilla/mozbase/blob/master/mozinfo/mozinfo.py)
+is the only file contained is this package,
+so if you need a single-file solution, you can just download or call
+this file through the web.
+
+The top level attributes (`os`, `version`, `bits`, `processor`) are
+available as module globals:
+
+ if mozinfo.os == 'win': ...
+
+In addition, mozinfo exports a dictionary, `mozinfo.info`, that
+contain these values. mozinfo also exports:
+
+- `choices`: a dictionary of possible values for os, bits, and
+ processor
+- `main`: the console_script entry point for mozinfo
+- `unknown`: a singleton denoting a value that cannot be determined
+
+`unknown` has the string representation `"UNKNOWN"`. unknown will evaluate
+as `False` in python:
+
+ if not mozinfo.os: ... # unknown!
+
+
+# Command Line Usage
+
+MozInfo comes with a command line, `mozinfo` which may be used to
+diagnose one's current system.
+
+Example output:
+
+ os: linux
+ version: Ubuntu 10.10
+ bits: 32
+ processor: x86
+
+Three of these fields, os, bits, and processor, have a finite set of
+choices. You may display the value of these choices using
+`mozinfo --os`, `mozinfo --bits`, and `mozinfo --processor`.
+`mozinfo --help` documents command-line usage.
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinfo/mozinfo.py
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozinfo.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Jeff Hammel <jhammel@mozilla.com>
+# Clint Talbert <ctalbert@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+"""
+file for interface to transform introspected system information to a format
+pallatable to Mozilla
+
+Information:
+- os : what operating system ['win', 'mac', 'linux', ...]
+- bits : 32 or 64
+- processor : processor architecture ['x86', 'x86_64', 'ppc', ...]
+- version : operating system version string
+
+For windows, the service pack information is also included
+"""
+
+# TODO: it might be a good idea of adding a system name (e.g. 'Ubuntu' for
+# linux) to the information; I certainly wouldn't want anyone parsing this
+# information and having behaviour depend on it
+
+import os
+import platform
+import re
+import sys
+
+# keep a copy of the os module since updating globals overrides this
+_os = os
+
+class unknown(object):
+ """marker class for unknown information"""
+ def __nonzero__(self):
+ return False
+ def __str__(self):
+ return 'UNKNOWN'
+unknown = unknown() # singleton
+
+# get system information
+info = {'os': unknown,
+ 'processor': unknown,
+ 'version': unknown,
+ 'bits': unknown }
+(system, node, release, version, machine, processor) = platform.uname()
+(bits, linkage) = platform.architecture()
+
+# get os information and related data
+if system in ["Microsoft", "Windows"]:
+ info['os'] = 'win'
+ # There is a Python bug on Windows to determine platform values
+ # http://bugs.python.org/issue7860
+ if "PROCESSOR_ARCHITEW6432" in os.environ:
+ processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor)
+ else:
+ processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor)
+ system = os.environ.get("OS", system).replace('_', ' ')
+ service_pack = os.sys.getwindowsversion()[4]
+ info['service_pack'] = service_pack
+elif system == "Linux":
+ (distro, version, codename) = platform.dist()
+ version = "%s %s" % (distro, version)
+ if not processor:
+ processor = machine
+ info['os'] = 'linux'
+elif system == "Darwin":
+ (release, versioninfo, machine) = platform.mac_ver()
+ version = "OS X %s" % release
+ info['os'] = 'mac'
+elif sys.platform in ('solaris', 'sunos5'):
+ info['os'] = 'unix'
+ version = sys.platform
+info['version'] = version # os version
+
+# processor type and bits
+if processor in ["i386", "i686"]:
+ if bits == "32bit":
+ processor = "x86"
+ elif bits == "64bit":
+ processor = "x86_64"
+elif processor == "AMD64":
+ bits = "64bit"
+ processor = "x86_64"
+elif processor == "Power Macintosh":
+ processor = "ppc"
+bits = re.search('(\d+)bit', bits).group(1)
+info.update({'processor': processor,
+ 'bits': int(bits),
+ })
+
+# standard value of choices, for easy inspection
+choices = {'os': ['linux', 'win', 'mac', 'unix'],
+ 'bits': [32, 64],
+ 'processor': ['x86', 'x86_64', 'ppc']}
+
+
+def sanitize(info):
+ """Do some sanitization of input values, primarily
+ to handle universal Mac builds."""
+ if "processor" in info and info["processor"] == "universal-x86-x86_64":
+ # If we're running on OS X 10.6 or newer, assume 64-bit
+ if release[:4] >= "10.6": # Note this is a string comparison
+ info["processor"] = "x86_64"
+ info["bits"] = 64
+ else:
+ info["processor"] = "x86"
+ info["bits"] = 32
+
+# method for updating information
+def update(new_info):
+ """update the info"""
+ info.update(new_info)
+ sanitize(info)
+ globals().update(info)
+
+ # convenience data for os access
+ for os_name in choices['os']:
+ globals()['is' + os_name.title()] = info['os'] == os_name
+ # unix is special
+ if isLinux:
+ globals()['isUnix'] = True
+
+update({})
+
+# exports
+__all__ = info.keys()
+__all__ += ['is' + os_name.title() for os_name in choices['os']]
+__all__ += ['info', 'unknown', 'main', 'choices', 'update']
+
+
+def main(args=None):
+
+ # parse the command line
+ from optparse import OptionParser
+ parser = OptionParser(description=__doc__)
+ for key in choices:
+ parser.add_option('--%s' % key, dest=key,
+ action='store_true', default=False,
+ help="display choices for %s" % key)
+ options, args = parser.parse_args()
+
+ # args are JSON blobs to override info
+ if args:
+ try:
+ from json import loads
+ except ImportError:
+ try:
+ from simplejson import loads
+ except ImportError:
+ def loads(string):
+ """*really* simple json; will not work with unicode"""
+ return eval(string, {'true': True, 'false': False, 'null': None})
+ for arg in args:
+ if _os.path.exists(arg):
+ string = file(arg).read()
+ else:
+ string = arg
+ update(loads(string))
+
+ # print out choices if requested
+ flag = False
+ for key, value in options.__dict__.items():
+ if value is True:
+ print '%s choices: %s' % (key, ' '.join([str(choice)
+ for choice in choices[key]]))
+ flag = True
+ if flag: return
+
+ # otherwise, print out all info
+ for key, value in info.items():
+ print '%s: %s' % (key, value)
+
+if __name__ == '__main__':
+ main()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinfo/setup.py
@@ -0,0 +1,78 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozinfo.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011.
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Jeff Hammel <jhammel@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+
+import os
+from setuptools import setup
+
+version = '0.3.3'
+
+# get documentation from the README
+try:
+ here = os.path.dirname(os.path.abspath(__file__))
+ description = file(os.path.join(here, 'README.md')).read()
+except (OSError, IOError):
+ description = ''
+
+# dependencies
+deps = []
+try:
+ import json
+except ImportError:
+ deps = ['simplejson']
+
+setup(name='mozinfo',
+ version=version,
+ description="file for interface to transform introspected system information to a format pallatable to Mozilla",
+ long_description=description,
+ classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ keywords='mozilla',
+ author='Jeff Hammel',
+ author_email='jhammel@mozilla.com',
+ url='https://wiki.mozilla.org/Auto-tools',
+ license='MPL',
+ py_modules=['mozinfo'],
+ packages=[],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=deps,
+ entry_points="""
+ # -*- Entry points: -*-
+ [console_scripts]
+ mozinfo = mozinfo:main
+ """,
+ )
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/README.md
@@ -0,0 +1,35 @@
+[Mozinstall](https://github.com/mozilla/mozbase/tree/master/mozinstall)
+is a python package for installing Mozilla applications on various platforms.
+
+For example, depending on the platform, Firefox can be distributed as a
+zip, tar.bz2, exe or dmg file or cloned from a repository. Mozinstall takes the
+hassle out of extracting and/or running these files and for convenience returns
+the full path to the application's binary in the install directory. In the case
+that mozinstall is invoked from the command line, the binary path will be
+printed to stdout.
+
+# Usage
+
+For command line options run mozinstall --help
+
+Mozinstall's main function is the install method
+
+ import mozinstall
+ mozinstall.install('path_to_install_file', dest='path_to_install_folder')
+
+The dest parameter defaults to the directory in which the install file is located.
+The install method accepts a third parameter called apps which tells mozinstall which
+binary to search for. By default it will search for 'firefox', 'thunderbird' and 'fennec'
+so unless you are installing a different application, this parameter is unnecessary.
+
+# Error Handling
+
+Mozinstall throws two different types of exceptions:
+
+- mozinstall.InvalidSource is thrown when the source is not a recognized file type (zip, exe, tar.bz2, tar.gz, dmg)
+- mozinstall.InstallError is thrown when the installation fails for any reason. A traceback is provided.
+
+# Dependencies
+
+Mozinstall depends on the [mozinfo](https://github.com/mozilla/mozbase/tree/master/mozinfo)
+package which is also found in the mozbase repository.
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/mozinstall.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozinstall.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Clint Talbert <ctalbert@mozilla.com>
+# Andrew Halberstadt <halbersa@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+from optparse import OptionParser
+import mozinfo
+import subprocess
+import zipfile
+import tarfile
+import sys
+import os
+
+_default_apps = ["firefox",
+ "thunderbird",
+ "fennec"]
+
+def install(src, dest=None, apps=_default_apps):
+ """
+ Installs a zip, exe, tar.gz, tar.bz2 or dmg file
+ src - the path to the install file
+ dest - the path to install to [default is os.path.dirname(src)]
+ returns - the full path to the binary in the installed folder
+ or None if the binary cannot be found
+ """
+ src = os.path.realpath(src)
+ assert(os.path.isfile(src))
+ if not dest:
+ dest = os.path.dirname(src)
+
+ trbk = None
+ try:
+ install_dir = None
+ if zipfile.is_zipfile(src) or tarfile.is_tarfile(src):
+ install_dir = _extract(src, dest)[0]
+ elif mozinfo.isMac and src.lower().endswith(".dmg"):
+ install_dir = _install_dmg(src, dest)
+ elif mozinfo.isWin and os.access(src, os.X_OK):
+ install_dir = _install_exe(src, dest)
+ else:
+ raise InvalidSource(src + " is not a recognized file type " +
+ "(zip, exe, tar.gz, tar.bz2 or dmg)")
+ except InvalidSource, e:
+ raise
+ except Exception, e:
+ cls, exc, trbk = sys.exc_info()
+ install_error = InstallError("Failed to install %s" % src)
+ raise install_error.__class__, install_error, trbk
+ finally:
+ # trbk won't get GC'ed due to circular reference
+ # http://docs.python.org/library/sys.html#sys.exc_info
+ del trbk
+
+ if install_dir:
+ return get_binary(install_dir, apps=apps)
+
+def get_binary(path, apps=_default_apps):
+ """
+ Finds the binary in the specified path
+ path - the path within which to search for the binary
+ returns - the full path to the binary in the folder
+ or None if the binary cannot be found
+ """
+ if mozinfo.isWin:
+ apps = [app + ".exe" for app in apps]
+ for root, dirs, files in os.walk(path):
+ for filename in files:
+ # os.access evaluates to False for some reason, so not using it
+ if filename in apps:
+ return os.path.realpath(os.path.join(root, filename))
+
+def _extract(path, extdir=None, delete=False):
+ """
+ Takes in a tar or zip file and extracts it to extdir
+ If extdir is not specified, extracts to os.path.dirname(path)
+ If delete is set to True, deletes the bundle at path
+ Returns the list of top level files that were extracted
+ """
+ if zipfile.is_zipfile(path):
+ bundle = zipfile.ZipFile(path)
+ namelist = bundle.namelist()
+ elif tarfile.is_tarfile(path):
+ bundle = tarfile.open(path)
+ namelist = bundle.getnames()
+ else:
+ return
+ if extdir is None:
+ extdir = os.path.dirname(path)
+ elif not os.path.exists(extdir):
+ os.makedirs(extdir)
+ bundle.extractall(path=extdir)
+ bundle.close()
+ if delete:
+ os.remove(path)
+ # namelist returns paths with forward slashes even in windows
+ top_level_files = [os.path.join(extdir, name) for name in namelist
+ if len(name.rstrip('/').split('/')) == 1]
+ # namelist doesn't include folders in windows, append these to the list
+ if mozinfo.isWin:
+ for name in namelist:
+ root = name[:name.find('/')]
+ if root not in top_level_files:
+ top_level_files.append(root)
+ return top_level_files
+
+def _install_dmg(src, dest):
+ proc = subprocess.Popen("hdiutil attach " + src,
+ shell=True,
+ stdout=subprocess.PIPE)
+ try:
+ for data in proc.communicate()[0].split():
+ if data.find("/Volumes/") != -1:
+ appDir = data
+ break
+ for appFile in os.listdir(appDir):
+ if appFile.endswith(".app"):
+ appName = appFile
+ break
+ subprocess.call("cp -r " + os.path.join(appDir, appName) + " " + dest,
+ shell=True)
+ finally:
+ subprocess.call("hdiutil detach " + appDir + " -quiet",
+ shell=True)
+ return os.path.join(dest, appName)
+
+def _install_exe(src, dest):
+ # possibly gets around UAC in vista (still need to run as administrator)
+ os.environ['__compat_layer'] = "RunAsInvoker"
+ cmd = [src, "/S", "/D=" + os.path.realpath(dest)]
+ subprocess.call(cmd)
+ return dest
+
+def cli(argv=sys.argv[1:]):
+ parser = OptionParser()
+ parser.add_option("-s", "--source",
+ dest="src",
+ help="Path to installation file. "
+ "Accepts: zip, exe, tar.bz2, tar.gz, and dmg")
+ parser.add_option("-d", "--destination",
+ dest="dest",
+ default=None,
+ help="[optional] Directory to install application into")
+ parser.add_option("--app", dest="app",
+ action="append",
+ default=_default_apps,
+ help="[optional] Application being installed. "
+ "Should be lowercase, e.g: "
+ "firefox, fennec, thunderbird, etc.")
+
+ (options, args) = parser.parse_args(argv)
+ if not options.src or not os.path.exists(options.src):
+ print "Error: must specify valid source"
+ return 2
+
+ # Run it
+ if os.path.isdir(options.src):
+ binary = get_binary(options.src, apps=options.app)
+ else:
+ binary = install(options.src, dest=options.dest, apps=options.app)
+ print binary
+
+class InvalidSource(Exception):
+ """
+ Thrown when the specified source is not a recognized
+ file type (zip, exe, tar.gz, tar.bz2 or dmg)
+ """
+
+class InstallError(Exception):
+ """
+ Thrown when the installation fails. Includes traceback
+ if available.
+ """
+
+if __name__ == "__main__":
+ sys.exit(cli())
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozinstall/setup.py
@@ -0,0 +1,79 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozinstall.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Clint Talbert <ctalbert@mozilla.com>
+# Andrew Halberstadt <halbersa@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import os
+from setuptools import setup
+
+try:
+ here = os.path.dirname(os.path.abspath(__file__))
+ description = file(os.path.join(here, 'README.md')).read()
+except IOError:
+ description = None
+
+version = '0.3'
+
+deps = ['mozinfo']
+
+setup(name='mozInstall',
+ version=version,
+ description="This is a utility package for installing Mozilla applications on various platforms.",
+ long_description=description,
+ classifiers=['Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)',
+ 'Natural Language :: English',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ keywords='mozilla',
+ author='mdas',
+ author_email='mdas@mozilla.com',
+ url='https://github.com/mozilla/mozbase',
+ license='MPL',
+ py_modules=['mozinstall'],
+ packages=[],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=deps,
+ entry_points="""
+ # -*- Entry points: -*-
+ [console_scripts]
+ mozinstall = mozinstall:cli
+ """,
+ )
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozlog/README.md
@@ -0,0 +1,18 @@
+[Mozlog](https://github.com/mozilla/mozbase/tree/master/mozlog)
+is a python package intended to simplify and standardize logs in the Mozilla universe.
+It wraps around python's [logging](http://docs.python.org/library/logging.html)
+module and adds some additional functionality.
+
+# Usage
+
+Import mozlog instead of [logging](http://docs.python.org/library/logging.html)
+(all functionality in the logging module is also available from the mozlog module).
+To get a logger, call mozlog.getLogger passing in a name and the path to a log file.
+If no log file is specified, the logger will log to stdout.
+
+ import mozlog
+ logger = mozlog.getLogger('LOG_NAME', 'log_file_path')
+ logger.setLevel(mozlog.DEBUG)
+ logger.info('foo')
+ logger.testPass('bar')
+ mozlog.shutdown()
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozlog/mozlog/__init__.py
@@ -0,0 +1,36 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/ #
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozlog.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Andrew Halberstadt <halbersa@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+from logger import *
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozlog/mozlog/logger.py
@@ -0,0 +1,125 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/ #
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozlog.
+#
+# The Initial Developer of the Original Code is
+# The Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Andrew Halberstadt <halbersa@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+from logging import getLogger as getSysLogger
+from logging import *
+
+_default_level = INFO
+_LoggerClass = getLoggerClass()
+
+# Define mozlog specific log levels
+START = _default_level + 1
+END = _default_level + 2
+PASS = _default_level + 3
+KNOWN_FAIL = _default_level + 4
+FAIL = _default_level + 5
+# Define associated text of log levels
+addLevelName(START, 'TEST-START')
+addLevelName(END, 'TEST-END')
+addLevelName(PASS, 'TEST-PASS')
+addLevelName(KNOWN_FAIL, 'TEST-KNOWN-FAIL')
+addLevelName(FAIL, 'TEST-UNEXPECTED-FAIL')
+
+class _MozLogger(_LoggerClass):
+ """
+ MozLogger class which adds three convenience log levels
+ related to automated testing in Mozilla
+ """
+ def testStart(self, message, *args, **kwargs):
+ self.log(START, message, *args, **kwargs)
+
+ def testEnd(self, message, *args, **kwargs):
+ self.log(END, message, *args, **kwargs)
+
+ def testPass(self, message, *args, **kwargs):
+ self.log(PASS, message, *args, **kwargs)
+
+ def testFail(self, message, *args, **kwargs):
+ self.log(FAIL, message, *args, **kwargs)
+
+ def testKnownFail(self, message, *args, **kwargs):