diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js
index 210f7a334c8e..15e14ab5da69 100644
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1024,7 +1024,7 @@ pref("apz.fling_curve_function_y1", "0.0");
pref("apz.fling_curve_function_x2", "0.80");
pref("apz.fling_curve_function_y2", "1.0");
pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
-pref("apz.fling_friction", "0.00238");
+pref("apz.fling_friction", "0.0019");
pref("apz.max_velocity_inches_per_ms", "0.07");
// Tweak default displayport values to reduce the risk of running out of
@@ -1039,9 +1039,9 @@ pref("apz.axis_lock.mode", 2);
// Overscroll-related settings
pref("apz.overscroll.enabled", true);
-pref("apz.overscroll.stretch_factor", "0.15");
-pref("apz.overscroll.spring_stiffness", "0.002");
-pref("apz.overscroll.spring_friction", "0.02");
+pref("apz.overscroll.stretch_factor", "0.35");
+pref("apz.overscroll.spring_stiffness", "0.0018");
+pref("apz.overscroll.spring_friction", "0.015");
pref("apz.overscroll.stop_distance_threshold", "5.0");
pref("apz.overscroll.stop_velocity_threshold", "0.01");
diff --git a/b2g/chrome/content/devtools/debugger.js b/b2g/chrome/content/devtools/debugger.js
index 45c56db88065..35911a20bc81 100644
--- a/b2g/chrome/content/devtools/debugger.js
+++ b/b2g/chrome/content/devtools/debugger.js
@@ -178,6 +178,11 @@ let RemoteDebugger = {
let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges);
+ // Allow debugging of chrome for any process
+ if (!restrictPrivileges) {
+ DebuggerServer.allowChromeProcess = true;
+ }
+
/**
* Construct a root actor appropriate for use in a server running in B2G.
* The returned root actor respects the factories registered with
diff --git a/browser/base/content/browser-readinglist.js b/browser/base/content/browser-readinglist.js
index 9e534837120a..3c5767474911 100644
--- a/browser/base/content/browser-readinglist.js
+++ b/browser/base/content/browser-readinglist.js
@@ -89,7 +89,7 @@ let ReadingListUI = {
}
},
- onReadingListPopupShowing(target) {
+ onReadingListPopupShowing: Task.async(function* (target) {
if (target.id == "BMB_readingListPopup") {
// Setting this class in the .xul file messes with the way
// browser-places.js inserts bookmarks in the menu.
@@ -105,55 +105,56 @@ let ReadingListUI = {
if (insertPoint.classList.contains("subviewbutton"))
classList += " subviewbutton";
- ReadingList.getItems().then(items => {
- for (let item of items) {
- let menuitem = document.createElement("menuitem");
- menuitem.setAttribute("label", item.title || item.url.spec);
- menuitem.setAttribute("class", classList);
+ let hasItems = false;
+ yield ReadingList.forEachItem(item => {
+ hasItems = true;
- let node = menuitem._placesNode = {
- // Passing the PlacesUtils.nodeIsURI check is required for the
- // onCommand handler to load our URI.
- type: Ci.nsINavHistoryResultNode.RESULT_TYPE_URI,
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", item.title || item.url);
+ menuitem.setAttribute("class", classList);
- // makes PlacesUIUtils.canUserRemove return false.
- // The context menu is broken without this.
- parent: {type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER},
+ let node = menuitem._placesNode = {
+ // Passing the PlacesUtils.nodeIsURI check is required for the
+ // onCommand handler to load our URI.
+ type: Ci.nsINavHistoryResultNode.RESULT_TYPE_URI,
- // A -1 id makes this item a non-bookmark, which avoids calling
- // PlacesUtils.annotations.itemHasAnnotation to check if the
- // bookmark should be opened in the sidebar (this call fails for
- // readinglist item, and breaks loading our URI).
- itemId: -1,
+ // makes PlacesUIUtils.canUserRemove return false.
+ // The context menu is broken without this.
+ parent: {type: Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER},
- // Used by the tooltip and onCommand handlers.
- uri: item.url.spec,
+ // A -1 id makes this item a non-bookmark, which avoids calling
+ // PlacesUtils.annotations.itemHasAnnotation to check if the
+ // bookmark should be opened in the sidebar (this call fails for
+ // readinglist item, and breaks loading our URI).
+ itemId: -1,
- // Used by the tooltip.
- title: item.title
- };
+ // Used by the tooltip and onCommand handlers.
+ uri: item.url,
- Favicons.getFaviconURLForPage(item.url, uri => {
- if (uri) {
- menuitem.setAttribute("image",
- Favicons.getFaviconLinkForIcon(uri).spec);
- }
- });
+ // Used by the tooltip.
+ title: item.title
+ };
- target.insertBefore(menuitem, insertPoint);
- }
+ Favicons.getFaviconURLForPage(item.uri, uri => {
+ if (uri) {
+ menuitem.setAttribute("image",
+ Favicons.getFaviconLinkForIcon(uri).spec);
+ }
+ });
- if (!items.length) {
- let menuitem = document.createElement("menuitem");
- let bundle =
- Services.strings.createBundle("chrome://browser/locale/places/places.properties");
- menuitem.setAttribute("label", bundle.GetStringFromName("bookmarksMenuEmptyFolder"));
- menuitem.setAttribute("class", "bookmark-item");
- menuitem.setAttribute("disabled", true);
- target.insertBefore(menuitem, insertPoint);
- }
+ target.insertBefore(menuitem, insertPoint);
});
- },
+
+ if (!hasItems) {
+ let menuitem = document.createElement("menuitem");
+ let bundle =
+ Services.strings.createBundle("chrome://browser/locale/places/places.properties");
+ menuitem.setAttribute("label", bundle.GetStringFromName("bookmarksMenuEmptyFolder"));
+ menuitem.setAttribute("class", "bookmark-item");
+ menuitem.setAttribute("disabled", true);
+ target.insertBefore(menuitem, insertPoint);
+ }
+ }),
/**
* Hide the ReadingList sidebar, if it is currently shown.
diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js
index eaba45b1df6b..8c4a82e9aad2 100644
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -637,8 +637,8 @@ SocialShare = {
// containing the open graph data.
let _dataFn;
if (!pageData || sharedURI == gBrowser.currentURI) {
- messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
- messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
+ messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
+ messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
let pageData = msg.json;
if (graphData) {
// overwrite data retreived from page with data given to us as a param
@@ -648,17 +648,17 @@ SocialShare = {
}
this.sharePage(providerOrigin, pageData, target);
});
- gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetPageData");
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");
return;
}
// if this is a share of a selected item, get any microdata
if (!pageData.microdata && target) {
- messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
- messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
+ messageManager.addMessageListener("PageMetadata:MicrodataResult", _dataFn = (msg) => {
+ messageManager.removeMessageListener("PageMetadata:MicrodataResult", _dataFn);
pageData.microdata = msg.data;
this.sharePage(providerOrigin, pageData, target);
});
- gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetMicrodata", null, target);
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicrodata", null, target);
return;
}
this.currentShare = pageData;
diff --git a/browser/base/content/content.js b/browser/base/content/content.js
index 7b65624d0aad..8c055d0055b1 100644
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -32,6 +32,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AboutReader",
"resource://gre/modules/AboutReader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
+ "resource://gre/modules/PageMetadata.jsm");
XPCOMUtils.defineLazyGetter(this, "SimpleServiceDiscovery", function() {
let ssdp = Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm", {}).SimpleServiceDiscovery;
// Register targets
@@ -153,6 +155,12 @@ let handleContentContextMenu = function (event) {
subject.wrappedJSObject = subject;
Services.obs.notifyObservers(subject, "content-contextmenu", null);
+ let doc = event.target.ownerDocument;
+ let docLocation = doc.location.href;
+ let charSet = doc.characterSet;
+ let baseURI = doc.baseURI;
+ let referrer = doc.referrer;
+
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
let editFlags = SpellCheckHelper.isEditable(event.target, content);
let spellInfo;
@@ -163,9 +171,10 @@ let handleContentContextMenu = function (event) {
}
let customMenuItems = PageMenuChild.build(event.target);
- let principal = event.target.ownerDocument.nodePrincipal;
+ let principal = doc.nodePrincipal;
sendSyncMessage("contextmenu",
- { editFlags, spellInfo, customMenuItems, addonInfo, principal },
+ { editFlags, spellInfo, customMenuItems, addonInfo,
+ principal, docLocation, charSet, baseURI, referrer },
{ event, popupNode: event.target });
}
else {
@@ -178,6 +187,10 @@ let handleContentContextMenu = function (event) {
popupNode: event.target,
browser: browser,
addonInfo: addonInfo,
+ documentURIObject: doc.documentURIObject,
+ docLocation: docLocation,
+ charSet: charSet,
+ referrer: referrer,
};
}
}
@@ -1000,30 +1013,29 @@ addEventListener("pageshow", function(event) {
}
});
-let SocialMessenger = {
+let PageMetadataMessenger = {
init: function() {
- addMessageListener("Social:GetPageData", this);
- addMessageListener("Social:GetMicrodata", this);
-
- XPCOMUtils.defineLazyGetter(this, "og", function() {
- let tmp = {};
- Cu.import("resource:///modules/Social.jsm", tmp);
- return tmp.OpenGraphBuilder;
- });
+ addMessageListener("PageMetadata:GetPageData", this);
+ addMessageListener("PageMetadata:GetMicrodata", this);
},
receiveMessage: function(aMessage) {
switch(aMessage.name) {
- case "Social:GetPageData":
- sendAsyncMessage("Social:PageDataResult", this.og.getData(content.document));
+ case "PageMetadata:GetPageData": {
+ let result = PageMetadata.getData(content.document);
+ sendAsyncMessage("PageMetadata:PageDataResult", result);
break;
- case "Social:GetMicrodata":
+ }
+
+ case "PageMetadata:GetMicrodata": {
let target = aMessage.objects;
- sendAsyncMessage("Social:PageDataResult", this.og.getMicrodata(content.document, target));
+ let result = PageMetadata.getMicrodata(content.document, target);
+ sendAsyncMessage("PageMetadata:MicrodataResult", result);
break;
+ }
}
}
}
-SocialMessenger.init();
+PageMetadataMessenger.init();
addEventListener("ActivateSocialFeature", function (aEvent) {
let document = content.document;
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
index 7d58b73ff73b..2b5bbba3cd6e 100644
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -576,6 +576,7 @@ nsContextMenu.prototype = {
this.linkURL = "";
this.linkURI = null;
this.linkProtocol = "";
+ this.linkHasNoReferrer = false;
this.onMathML = false;
this.inFrame = false;
this.inSrcdocFrame = false;
@@ -738,6 +739,7 @@ nsContextMenu.prototype = {
this.linkProtocol = this.getLinkProtocol();
this.onMailtoLink = (this.linkProtocol == "mailto");
this.onSaveableLink = this.isLinkSaveable( this.link );
+ this.linkHasNoReferrer = BrowserUtils.linkHasNoReferrer(elem);
}
// Background image? Don't bother if we've already found a
@@ -864,10 +866,10 @@ nsContextMenu.prototype = {
return aNode.spellcheck;
},
- _openLinkInParameters : function (doc, extra) {
- let params = { charset: doc.characterSet,
- referrerURI: doc.documentURIObject,
- noReferrer: BrowserUtils.linkHasNoReferrer(this.link) };
+ _openLinkInParameters : function (extra) {
+ let params = { charset: gContextMenuContentData.charSet,
+ referrerURI: gContextMenuContentData.documentURIObject,
+ noReferrer: this.linkHasNoReferrer };
for (let p in extra)
params[p] = extra[p];
return params;
@@ -875,41 +877,38 @@ nsContextMenu.prototype = {
// Open linked-to URL in a new window.
openLink : function () {
- var doc = this.target.ownerDocument;
urlSecurityCheck(this.linkURL, this.principal);
- openLinkIn(this.linkURL, "window", this._openLinkInParameters(doc));
+ openLinkIn(this.linkURL, "window", this._openLinkInParameters());
},
// Open linked-to URL in a new private window.
openLinkInPrivateWindow : function () {
- var doc = this.target.ownerDocument;
urlSecurityCheck(this.linkURL, this.principal);
openLinkIn(this.linkURL, "window",
- this._openLinkInParameters(doc, { private: true }));
+ this._openLinkInParameters({ private: true }));
},
// Open linked-to URL in a new tab.
openLinkInTab: function() {
- var doc = this.target.ownerDocument;
urlSecurityCheck(this.linkURL, this.principal);
- var referrerURI = doc.documentURIObject;
+ let referrerURI = gContextMenuContentData.documentURIObject;
// if the mixedContentChannel is present and the referring URI passes
// a same origin check with the target URI, we can preserve the users
// decision of disabling MCB on a page for it's child tabs.
- var persistAllowMixedContentInChildTab = false;
+ let persistAllowMixedContentInChildTab = false;
if (this.browser.docShell && this.browser.docShell.mixedContentChannel) {
const sm = Services.scriptSecurityManager;
try {
- var targetURI = this.linkURI;
+ let targetURI = this.linkURI;
sm.checkSameOriginURI(referrerURI, targetURI, false);
persistAllowMixedContentInChildTab = true;
}
catch (e) { }
}
- let params = this._openLinkInParameters(doc, {
+ let params = this._openLinkInParameters({
allowMixedContent: persistAllowMixedContentInChildTab,
});
openLinkIn(this.linkURL, "tab", params);
@@ -917,18 +916,15 @@ nsContextMenu.prototype = {
// open URL in current tab
openLinkInCurrent: function() {
- var doc = this.target.ownerDocument;
urlSecurityCheck(this.linkURL, this.principal);
- openLinkIn(this.linkURL, "current", this._openLinkInParameters(doc));
+ openLinkIn(this.linkURL, "current", this._openLinkInParameters());
},
// Open frame in a new tab.
openFrameInTab: function() {
- var doc = this.target.ownerDocument;
- var frameURL = doc.location.href;
- var referrer = doc.referrer;
- openLinkIn(frameURL, "tab",
- { charset: doc.characterSet,
+ let referrer = gContextMenuContentData.referrer;
+ openLinkIn(gContextMenuContentData.docLocation, "tab",
+ { charset: gContextMenuContentData.charSet,
referrerURI: referrer ? makeURI(referrer) : null });
},
@@ -939,25 +935,21 @@ nsContextMenu.prototype = {
// Open clicked-in frame in its own window.
openFrame: function() {
- var doc = this.target.ownerDocument;
- var frameURL = doc.location.href;
- var referrer = doc.referrer;
- openLinkIn(frameURL, "window",
- { charset: doc.characterSet,
+ let referrer = gContextMenuContentData.referrer;
+ openLinkIn(gContextMenuContentData.docLocation, "window",
+ { charset: gContextMenuContentData.charSet,
referrerURI: referrer ? makeURI(referrer) : null });
},
// Open clicked-in frame in the same window.
showOnlyThisFrame: function() {
- var doc = this.target.ownerDocument;
- var frameURL = doc.location.href;
-
- urlSecurityCheck(frameURL,
+ urlSecurityCheck(gContextMenuContentData.docLocation,
this.browser.contentPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
- var referrer = doc.referrer;
- openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true,
- referrerURI: referrer ? makeURI(referrer) : null });
+ let referrer = gContextMenuContentData.referrer;
+ openUILinkIn(gContextMenuContentData.docLocation, "current",
+ { disallowInheritPrincipal: true,
+ referrerURI: referrer ? makeURI(referrer) : null });
},
reload: function(event) {
diff --git a/browser/base/content/socialmarks.xml b/browser/base/content/socialmarks.xml
index 3498efd7cd43..fa6de9dcde1b 100644
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -150,21 +150,21 @@
let URLTemplate = provider.markURL;
let _dataFn;
if (!pageData) {
- messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
- messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
+ messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
+ messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
this.loadPanel(msg.json, target);
});
- gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetPageData");
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");
return;
}
// if this is a share of a selected item, get any microdata
if (!pageData.microdata && target) {
- messageManager.addMessageListener("Social:PageDataResult", _dataFn = (msg) => {
- messageManager.removeMessageListener("Social:PageDataResult", _dataFn);
+ messageManager.addMessageListener("PageMetadata:MicrodataResult", _dataFn = (msg) => {
+ messageManager.removeMessageListener("PageMetadata:MicrodataResult", _dataFn);
pageData.microdata = msg.data;
this.loadPanel(pageData, target);
});
- gBrowser.selectedBrowser.messageManager.sendAsyncMessage("Social:GetMicrodata", null, target);
+ gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicrodata", null, target);
return;
}
this.pageData = pageData;
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index 0db907fcdf73..dddc2164f789 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3218,6 +3218,9 @@
let spellInfo = aMessage.data.spellInfo;
if (spellInfo)
spellInfo.target = aMessage.target.messageManager;
+ let documentURIObject = makeURI(aMessage.data.docLocation,
+ aMessage.data.charSet,
+ makeURI(aMessage.data.baseURI));
gContextMenuContentData = { isRemote: true,
event: aMessage.objects.event,
popupNode: aMessage.objects.popupNode,
@@ -3226,7 +3229,11 @@
spellInfo: spellInfo,
principal: aMessage.data.principal,
customMenuItems: aMessage.data.customMenuItems,
- addonInfo: aMessage.data.addonInfo };
+ addonInfo: aMessage.data.addonInfo,
+ documentURIObject: documentURIObject,
+ docLocation: aMessage.data.docLocation,
+ charSet: aMessage.data.charSet,
+ referrer: aMessage.data.referrer };
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
let event = gContextMenuContentData.event;
let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);
diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini
index da8d1d35ea15..209c444bb229 100644
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -12,6 +12,7 @@ support-files =
browser_bug678392-2.html
browser_bug970746.xhtml
browser_fxa_oauth.html
+ browser_fxa_profile_channel.html
browser_registerProtocolHandler_notification.html
browser_ssl_error_reports_content.js
browser_star_hsts.sjs
@@ -319,6 +320,7 @@ skip-if = e10s
skip-if = buildapp == 'mulet' || e10s || os == "linux" # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly. Linux: Intermittent failures - bug 941575.
[browser_fxa_migrate.js]
[browser_fxa_oauth.js]
+[browser_fxa_profile_channel.js]
[browser_gestureSupport.js]
skip-if = e10s # Bug 863514 - no gesture support.
[browser_getshortcutoruri.js]
diff --git a/browser/base/content/test/general/browser_bug734076.js b/browser/base/content/test/general/browser_bug734076.js
index 3e1cae71693a..b58db66e08d6 100644
--- a/browser/base/content/test/general/browser_bug734076.js
+++ b/browser/base/content/test/general/browser_bug734076.js
@@ -26,8 +26,7 @@ function test() {
cb();
});
- let contextMenu = initContextMenu(contentBody);
- contextMenu.viewBGImage();
+ doContextCommand(contentBody, "context-viewbgimage");
}
},
{
@@ -45,8 +44,7 @@ function test() {
img.setAttribute("src", writeDomainURL);
doc.body.appendChild(img);
- let contextMenu = initContextMenu(img);
- contextMenu.viewMedia();
+ doContextCommand(img, "context-viewimage");
}
},
{
@@ -65,8 +63,8 @@ function test() {
doc.body.appendChild(iframe);
iframe.addEventListener("load", function onload() {
- let contextMenu = initContextMenu(iframe.contentDocument.body);
- contextMenu.showOnlyThisFrame();
+ doContextCommand(iframe.contentDocument.body,
+ "context-showonlythisframe");
}, false);
}
}
@@ -99,9 +97,8 @@ function test() {
doNext();
}
-function initContextMenu(aNode) {
- document.popupNode = aNode;
- let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
- let contextMenu = new nsContextMenu(contentAreaContextMenu);
- return contextMenu;
+function doContextCommand(aNode, aCmd) {
+ EventUtils.sendMouseEvent({ type: "contextmenu" }, aNode);
+ document.getElementById(aCmd).click();
+ document.getElementById("contentAreaContextMenu").hidePopup();
}
diff --git a/browser/base/content/test/general/browser_fxa_profile_channel.html b/browser/base/content/test/general/browser_fxa_profile_channel.html
new file mode 100644
index 000000000000..2382eaad5e69
--- /dev/null
+++ b/browser/base/content/test/general/browser_fxa_profile_channel.html
@@ -0,0 +1,26 @@
+
+
+
+ = 34,
- window: OT.$.env.name === 'Firefox' && OT.$.env.version >= 34
+ window: OT.$.env.name === 'Firefox' && OT.$.env.version >= 34,
+ browser: OT.$.env.name === 'Firefox' && OT.$.env.version >= 38
},
register: function() {
return {
@@ -16451,6 +16513,17 @@ OT.registerScreenSharingExtensionHelper('firefox', {
constraints.video = {
mediaSource: source
};
+
+ // copy constraints under the video object and removed them from the root constraint object
+ if (constraints.browserWindow) {
+ constraints.video.browserWindow = constraints.browserWindow;
+ delete constraints.browserWindow;
+ }
+ if (typeof constraints.scrollWithPage !== 'undefined') {
+ constraints.video.scrollWithPage = constraints.scrollWithPage;
+ delete constraints.scrollWithPage;
+ }
+
callback(void 0, constraints);
}
};
@@ -16465,7 +16538,8 @@ OT.registerScreenSharingExtensionHelper('chrome', {
sources: {
screen: true,
application: false,
- window: false
+ window: false,
+ browser: false
},
register: function (extensionID) {
if(!extensionID) {
@@ -16755,8 +16829,7 @@ OT.StreamChannel = function(options) {
* encoded stream; the height
property is the height of the encoded stream. (These
* are independent of the actual width of Publisher and Subscriber objects corresponding to the
* stream.) This property can change if a stream published from a mobile device resizes, based on
- * a change in the device orientation. It can also occur if the video source is a screen-sharing
- * window and the user publishing the stream resizes the window. When the video dimensions change,
+ * a change in the device orientation. When the video dimensions change,
* the {@link Session} object dispatches a streamPropertyChanged
event
* (see {@link StreamPropertyChangedEvent}).
*
@@ -17074,8 +17147,7 @@ OT.StreamChannel = function(options) {
}
};
}
- session.logEvent('SessionInfo', 'Attempt', {messagingServer: OT.properties.apiURL});
-
+ session.logEvent('SessionInfo', 'Attempt');
OT.$.getJSON(sessionInfoURL, options, function(error, sessionInfo) {
if(error) {
var responseText = sessionInfo;
@@ -17125,11 +17197,14 @@ OT.StreamChannel = function(options) {
}
};
+ /* jshint camelcase:false */
onGetResponseCallback = function(session, onSuccess, rawSessionInfo) {
- session.logEvent('SessionInfo', 'Success', {messagingServer: OT.properties.apiURL});
+ session.logEvent('SessionInfo', 'Success',
+ {messagingServer: rawSessionInfo[0].messaging_server_url});
onSuccess( new OT.SessionInfo(rawSessionInfo) );
};
+ /* jshint camelcase:true */
onGetErrorCallback = function(session, onFailure, error, responseText) {
var payload = {
@@ -18254,10 +18329,9 @@ OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphony
};
// Register a new stream against _sessionId
- this.streamCreate = function(name, audioFallbackEnabled, channels, minBitrate, maxBitrate,
- completion) {
- var streamId = OT.$.uuid(),
- message = OT.Raptor.Message.streams.create( OT.APIKEY,
+ this.streamCreate = function(name, streamId, audioFallbackEnabled, channels, minBitrate,
+ maxBitrate, completion) {
+ var message = OT.Raptor.Message.streams.create( OT.APIKEY,
_sessionId,
streamId,
name,
@@ -18443,24 +18517,31 @@ OT.AnalyserAudioLevelSampler = function(audioContext) {
var _sampler = this,
_analyser = null,
- _timeDomainData = null;
+ _timeDomainData = null,
+ _webRTCStream = null;
- var _getAnalyser = function(stream) {
- var sourceNode = audioContext.createMediaStreamSource(stream);
- var analyser = audioContext.createAnalyser();
- sourceNode.connect(analyser);
- return analyser;
- };
+ var buildAnalyzer = function(stream) {
+ var sourceNode = audioContext.createMediaStreamSource(stream);
+ var analyser = audioContext.createAnalyser();
+ sourceNode.connect(analyser);
+ return analyser;
+ };
- this.webRTCStream = null;
+ OT.$.defineProperties(_sampler, {
+ webRTCStream: {
+ get: function() {
+ return _webRTCStream;
+ },
+ set: function(webRTCStream) {
+ // when the stream is updated we need to create a new analyzer
+ _webRTCStream = webRTCStream;
+ _analyser = buildAnalyzer(_webRTCStream);
+ _timeDomainData = new Uint8Array(_analyser.frequencyBinCount);
+ }
+ }
+ });
this.sample = function(done) {
-
- if (!_analyser && _sampler.webRTCStream) {
- _analyser = _getAnalyser(_sampler.webRTCStream);
- _timeDomainData = new Uint8Array(_analyser.frequencyBinCount);
- }
-
if (_analyser) {
_analyser.getByteTimeDomainData(_timeDomainData);
@@ -18886,7 +18967,7 @@ OT.Subscriber = function(targetElement, options, completionHandler) {
if (OT.$.hasCapabilities('webAudioCapableRemoteStream') && _audioLevelSampler &&
webRTCStream.getAudioTracks().length > 0) {
- _audioLevelSampler.webRTCStream = webRTCStream;
+ _audioLevelSampler.webRTCStream(webRTCStream);
}
logAnalyticsEvent('createPeerConnection', 'StreamAdded');
@@ -20326,16 +20407,16 @@ OT.Session = function(apiKey, sessionId) {
var origCallback = callback;
callback = function loggingCallback(error, stats) {
if (error) {
- _session.logEvent('testNetwork', 'Failure', {
+ _session.logEvent('TestNetwork', 'Failure', {
failureCode: error.name || error.message || 'unknown'
});
} else {
- _session.logEvent('testNetwork', 'Success', stats);
+ _session.logEvent('TestNetwork', 'Success', stats);
}
origCallback(error, stats);
};
- _session.logEvent('testNetwork', 'Attempt', {});
+ _session.logEvent('TestNetwork', 'Attempt', {});
if(this.isConnected()) {
callback(new OT.$.Error('Session connected, cannot test network', 1015));
@@ -20801,6 +20882,7 @@ OT.Session = function(apiKey, sessionId) {
message: 'We need to be connected before you can publish'
},
sessionId: _sessionId,
+ streamId: (publisher && publisher.stream) ? publisher.stream.id : null,
partnerId: _apiKey,
});
@@ -21392,9 +21474,10 @@ OT.Session = function(apiKey, sessionId) {
attributes);
},
- streamCreate: function(name, audioFallbackEnabled, channels, completion) {
+ streamCreate: function(name, streamId, audioFallbackEnabled, channels, completion) {
_socket.streamCreate(
name,
+ streamId,
audioFallbackEnabled,
channels,
OT.Config.get('bitrates', 'min', OT.APIKEY),
@@ -21992,7 +22075,7 @@ OT.Session = function(apiKey, sessionId) {
/* jshint globalstrict: true, strict: false, undef: true, unused: true,
trailing: true, browser: true, smarttabs:true */
-/* global OT */
+/* global OT, Promise */
// The default constraints
var defaultConstraints = {
@@ -22080,6 +22163,7 @@ OT.Publisher = function(options) {
options.videoSource === 'screen' ||
options.videoSource === 'window' ||
options.videoSource === 'tab' ||
+ options.videoSource === 'browser' ||
options.videoSource === 'application'
),
_connectivityAttemptPinger,
@@ -22151,7 +22235,7 @@ OT.Publisher = function(options) {
'connectionId': _session &&
_session.isConnected() ? _session.connection.connectionId : null,
'partnerId': _session ? _session.apiKey : OT.APIKEY,
- streamId: _stream ? _stream.id : null
+ streamId: _streamId
}, throttle);
},
@@ -22163,7 +22247,7 @@ OT.Publisher = function(options) {
'connectionId': _session &&
_session.isConnected() ? _session.connection.connectionId : null,
'partnerId': _session ? _session.apiKey : OT.APIKEY,
- streamId: _stream ? _stream.id : null
+ streamId: _streamId
});
}
if (variation === 'Failure' && payload.reason !== 'Non-fatal') {
@@ -22181,7 +22265,7 @@ OT.Publisher = function(options) {
connectionId: _session && _session.isConnected() ?
_session.connection.connectionId : null,
partnerId: _session ? _session.apiKey : OT.APIKEY,
- streamId: _stream ? _stream.id : null,
+ streamId: _streamId,
width: _widgetView ? Number(OT.$.width(_widgetView.domElement).replace('px', ''))
: undefined,
height: _widgetView ? Number(OT.$.height(_widgetView.domElement).replace('px', ''))
@@ -22278,7 +22362,7 @@ OT.Publisher = function(options) {
});
if(_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) {
- _audioLevelSampler.webRTCStream = _webRTCStream;
+ _audioLevelSampler.webRTCStream(_webRTCStream);
}
}, this),
@@ -22985,7 +23069,6 @@ OT.Publisher = function(options) {
return this;
};
-
/**
* Deletes the Publisher object and removes it from the HTML DOM.
*
@@ -23093,6 +23176,7 @@ OT.Publisher = function(options) {
// Add session property to Publisher
this.session = _session = session;
+ _streamId = OT.$.uuid();
var createStream = function() {
var streamWidth,
@@ -23161,8 +23245,8 @@ OT.Publisher = function(options) {
}));
}
- session._.streamCreate(_properties.name || '', _properties.audioFallbackEnabled,
- streamChannels, onStreamRegistered);
+ session._.streamCreate(_properties.name || '', _streamId,
+ _properties.audioFallbackEnabled, streamChannels, onStreamRegistered);
};
@@ -23227,6 +23311,75 @@ OT.Publisher = function(options) {
webRtcStream: function() {
return _webRTCStream;
+ },
+
+ /**
+ * @param {string=} windowId
+ */
+ switchAcquiredWindow: function(windowId) {
+
+ if (OT.$.env.name !== 'Firefox' || OT.$.env.version < 38) {
+ throw new Error('switchAcquiredWindow is an experimental method and is not supported by' +
+ 'the current platform');
+ }
+
+ if (typeof windowId !== 'undefined') {
+ _properties.constraints.video.browserWindow = windowId;
+ }
+
+ return new Promise(function(resolve, reject) {
+ OT.$.getUserMedia(
+ _properties.constraints,
+ function(newStream) {
+
+ cleanupLocalStream();
+ _webRTCStream = newStream;
+
+ _microphone = new OT.Microphone(_webRTCStream, !_properties.publishAudio);
+
+ var videoContainerOptions = {
+ muted: true,
+ error: onVideoError
+ };
+
+ _targetElement = _widgetView.bindVideo(_webRTCStream, videoContainerOptions,
+ function(err) {
+ if (err) {
+ onLoadFailure(err);
+ reject(err);
+ }
+ });
+
+ if (_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) {
+ _audioLevelSampler.webRTCStream(_webRTCStream);
+ }
+
+ var replacePromises = [];
+
+ Object.keys(_peerConnections).forEach(function(connectionId) {
+ var peerConnection = _peerConnections[connectionId];
+ peerConnection.getSenders().forEach(function(sender) {
+ if (sender.track.kind === 'audio' && newStream.getAudioTracks().length) {
+ replacePromises.push(sender.replaceTrack(newStream.getAudioTracks()[0]));
+ } else if (sender.track.kind === 'video' && newStream.getVideoTracks().length) {
+ replacePromises.push(sender.replaceTrack(newStream.getVideoTracks()[0]));
+ }
+ });
+ });
+
+ Promise.all(replacePromises).then(resolve, reject);
+ },
+ function(error) {
+ onStreamAvailableError(error);
+ reject(error);
+ },
+ onAccessDialogOpened,
+ onAccessDialogClosed,
+ function(error) {
+ onAccessDenied(error);
+ reject(error);
+ });
+ });
}
};
@@ -23706,7 +23859,8 @@ OT.initSession = function(apiKey, sessionId) {
* property to null
or false
for each Publisher.
*
*
-* Set this property to "screen"
to publish a screen-sharing stream. Call
+* To publish a screen-sharing streamet this property to "application"
,
+* "screen"
, or "window"
. Call
* OT.checkScreenSharingCapability() to check
* if screen sharing is supported. When you set the videoSource
property to
* "screen"
, the following are default values for other properties:
diff --git a/browser/components/loop/test/desktop-local/roomViews_test.js b/browser/components/loop/test/desktop-local/roomViews_test.js
index edddf14b9728..886253276353 100644
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -22,6 +22,7 @@ describe("loop.roomViews", function () {
};
fakeWindow = {
+ close: sinon.stub(),
document: {},
navigator: {
mozLoop: fakeMozLoop
@@ -290,6 +291,32 @@ describe("loop.roomViews", function () {
sinon.match.hasOwn("name", "setMute"));
});
+ it("should dispatch a `LeaveRoom` action when the hangup button is pressed and the room has been used", function() {
+ view = mountTestComponent();
+
+ view.setState({used: true});
+
+ var hangupBtn = view.getDOMNode().querySelector(".btn-hangup");
+
+ React.addons.TestUtils.Simulate.click(hangupBtn);
+
+ sinon.assert.calledOnce(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
+ new sharedActions.LeaveRoom());
+ });
+
+ it("should close the window when the hangup button is pressed and the room has not been used", function() {
+ view = mountTestComponent();
+
+ view.setState({used: false});
+
+ var hangupBtn = view.getDOMNode().querySelector(".btn-hangup");
+
+ React.addons.TestUtils.Simulate.click(hangupBtn);
+
+ sinon.assert.calledOnce(fakeWindow.close);
+ });
+
describe("#componentWillUpdate", function() {
function expectActionDispatched(view) {
sinon.assert.calledOnce(dispatcher.dispatch);
@@ -389,18 +416,6 @@ describe("loop.roomViews", function () {
TestUtils.findRenderedComponentWithType(view,
loop.shared.views.FeedbackView);
});
-
- it("should NOT render the FeedbackView if the room has not been used",
- function() {
- activeRoomStore.setStoreState({
- roomState: ROOM_STATES.ENDED,
- used: false
- });
-
- view = mountTestComponent();
-
- expect(view.getDOMNode()).eql(null);
- });
});
describe("Mute", function() {
diff --git a/browser/components/loop/test/shared/activeRoomStore_test.js b/browser/components/loop/test/shared/activeRoomStore_test.js
index df5395aeb4d3..fa36c1e311ac 100644
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -142,6 +142,15 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledOnce(fakeMultiplexGum.reset);
});
+ it("should set screen sharing inactive", function() {
+ store.setStoreState({windowId: "1234"});
+
+ store.roomFailure({error: fakeError});
+
+ sinon.assert.calledOnce(fakeMozLoop.setScreenShareState);
+ sinon.assert.calledWithExactly(fakeMozLoop.setScreenShareState, "1234", false);
+ });
+
it("should disconnect from the servers via the sdk", function() {
store.roomFailure({error: fakeError});
@@ -157,6 +166,18 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledOnce(clearTimeout);
});
+ it("should remove the sharing listener", function() {
+ // Setup the listener.
+ store.startScreenShare(new sharedActions.StartScreenShare({
+ type: "browser"
+ }));
+
+ // Now simulate room failure.
+ store.roomFailure({error: fakeError});
+
+ sinon.assert.calledOnce(fakeMozLoop.removeBrowserSharingListener);
+ });
+
it("should call mozLoop.rooms.leave", function() {
store.roomFailure({error: fakeError});
@@ -600,6 +621,15 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledOnce(fakeMultiplexGum.reset);
});
+ it("should set screen sharing inactive", function() {
+ store.setStoreState({windowId: "1234"});
+
+ store.connectionFailure(connectionFailureAction);
+
+ sinon.assert.calledOnce(fakeMozLoop.setScreenShareState);
+ sinon.assert.calledWithExactly(fakeMozLoop.setScreenShareState, "1234", false);
+ });
+
it("should disconnect from the servers via the sdk", function() {
store.connectionFailure(connectionFailureAction);
@@ -623,6 +653,18 @@ describe("loop.store.ActiveRoomStore", function () {
"fakeToken", "1627384950");
});
+ it("should remove the sharing listener", function() {
+ // Setup the listener.
+ store.startScreenShare(new sharedActions.StartScreenShare({
+ type: "browser"
+ }));
+
+ // Now simulate connection failure.
+ store.connectionFailure(connectionFailureAction);
+
+ sinon.assert.calledOnce(fakeMozLoop.removeBrowserSharingListener);
+ });
+
it("should set the state to `FAILED`", function() {
store.connectionFailure(connectionFailureAction);
@@ -877,6 +919,18 @@ describe("loop.store.ActiveRoomStore", function () {
"fakeToken", "1627384950");
});
+ it("should remove the sharing listener", function() {
+ // Setup the listener.
+ store.startScreenShare(new sharedActions.StartScreenShare({
+ type: "browser"
+ }));
+
+ // Now unload the window.
+ store.windowUnload();
+
+ sinon.assert.calledOnce(fakeMozLoop.removeBrowserSharingListener);
+ });
+
it("should set the state to CLOSING", function() {
store.windowUnload();
@@ -922,6 +976,18 @@ describe("loop.store.ActiveRoomStore", function () {
"fakeToken", "1627384950");
});
+ it("should remove the sharing listener", function() {
+ // Setup the listener.
+ store.startScreenShare(new sharedActions.StartScreenShare({
+ type: "browser"
+ }));
+
+ // Now leave the room.
+ store.leaveRoom();
+
+ sinon.assert.calledOnce(fakeMozLoop.removeBrowserSharingListener);
+ });
+
it("should set the state to ENDED", function() {
store.leaveRoom();
diff --git a/browser/components/readinglist/ReadingList.jsm b/browser/components/readinglist/ReadingList.jsm
index dcd1cc808a55..5e3eeb3aa2da 100644
--- a/browser/components/readinglist/ReadingList.jsm
+++ b/browser/components/readinglist/ReadingList.jsm
@@ -1,21 +1,26 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-this.EXPORTED_SYMBOLS = ["ReadingList"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+this.EXPORTED_SYMBOLS = [
+ "ReadingList",
+];
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Log.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SQLiteStore",
+ "resource:///modules/readinglist/SQLiteStore.jsm");
-(function() {
+
+{ // Prevent the parent log setup from leaking into the global scope.
let parentLog = Log.repository.getLogger("readinglist");
parentLog.level = Preferences.get("browser.readinglist.logLevel", Log.Level.Warn);
Preferences.observe("browser.readinglist.logLevel", value => {
@@ -24,333 +29,823 @@ Cu.import("resource://gre/modules/Log.jsm");
let formatter = new Log.BasicFormatter();
parentLog.addAppender(new Log.ConsoleAppender(formatter));
parentLog.addAppender(new Log.DumpAppender(formatter));
-})();
-
+}
let log = Log.repository.getLogger("readinglist.api");
+
+// Names of basic properties on ReadingListItem.
+const ITEM_BASIC_PROPERTY_NAMES = `
+ guid
+ lastModified
+ url
+ title
+ resolvedURL
+ resolvedTitle
+ excerpt
+ status
+ favorite
+ isArticle
+ wordCount
+ unread
+ addedBy
+ addedOn
+ storedOn
+ markedReadBy
+ markedReadOn
+ readPosition
+`.trim().split(/\s+/);
+
/**
- * Represents an item in the Reading List.
- * @constructor
- * @see https://github.com/mozilla-services/readinglist/wiki/API-Design-proposal#data-model
+ * A reading list contains ReadingListItems.
+ *
+ * A list maintains only one copy of an item per URL. So if for example you use
+ * an iterator to get two references to items with the same URL, your references
+ * actually refer to the same JS object.
+ *
+ * Options Objects
+ * ---------------
+ *
+ * Some methods on ReadingList take an "optsList", a variable number of
+ * arguments, each of which is an "options object". Options objects let you
+ * control the items that the method acts on.
+ *
+ * Each options object is a simple object with properties whose names are drawn
+ * from ITEM_BASIC_PROPERTY_NAMES. For an item to match an options object, the
+ * properties of the item must match all the properties in the object. For
+ * example, an object { guid: "123" } matches any item whose GUID is 123. An
+ * object { guid: "123", title: "foo" } matches any item whose GUID is 123 *and*
+ * whose title is foo.
+ *
+ * You can pass multiple options objects as separate arguments. For an item to
+ * match multiple objects, its properties must match all the properties in at
+ * least one of the objects. For example, a list of objects { guid: "123" } and
+ * { title: "foo" } matches any item whose GUID is 123 *or* whose title is
+ * foo.
+ *
+ * The properties in an options object can be arrays, not only scalars. When a
+ * property is an array, then for an item to match, its corresponding property
+ * must have a value that matches any value in the array. For example, an
+ * options object { guid: ["123", "456"] } matches any item whose GUID is either
+ * 123 *or* 456.
+ *
+ * In addition to properties with names from ITEM_BASIC_PROPERTY_NAMES, options
+ * objects can also have the following special properties:
+ *
+ * * sort: The name of a property to sort on.
+ * * descending: A boolean, true to sort descending, false to sort ascending.
+ * If `sort` is given but `descending` isn't, the sort is ascending (since
+ * `descending` is falsey).
+ * * limit: Limits the number of matching items to this number.
+ * * offset: Starts matching items at this index in the results.
+ *
+ * Since you can pass multiple options objects in a list, you can include these
+ * special properties in any number of the objects in the list, but it doesn't
+ * really make sense to do so. The last property in the list is the one that's
+ * used.
+ *
+ * @param store Backing storage for the list. See SQLiteStore.jsm for what this
+ * object's interface should look like.
*/
-function Item(data) {
- this._data = data;
+function ReadingListImpl(store) {
+ this._store = store;
+ this._itemsByURL = new Map();
+ this._iterators = new Set();
+ this._listeners = new Set();
}
-Item.prototype = {
+ReadingListImpl.prototype = {
+
+ ItemBasicPropertyNames: ITEM_BASIC_PROPERTY_NAMES,
+
/**
- * UUID
- * @type {string}
+ * Yields the number of items in the list.
+ *
+ * @param optsList A variable number of options objects that control the
+ * items that are matched. See Options Objects.
+ * @return Promise The number of matching items in the list. Rejected
+ * with an Error on error.
*/
- get id() {
- return this._data.id;
+ count: Task.async(function* (...optsList) {
+ return (yield this._store.count(...optsList));
+ }),
+
+ /**
+ * Enumerates the items in the list that match the given options.
+ *
+ * @param callback Called for each item in the enumeration. It's passed a
+ * single object, a ReadingListItem. It may return a promise; if so,
+ * the callback will not be called for the next item until the promise
+ * is resolved.
+ * @param optsList A variable number of options objects that control the
+ * items that are matched. See Options Objects.
+ * @return Promise Resolved when the enumeration completes *and* the
+ * last promise returned by the callback is resolved. Rejected with
+ * an Error on error.
+ */
+ forEachItem: Task.async(function* (callback, ...optsList) {
+ let promiseChain = Promise.resolve();
+ yield this._store.forEachItem(obj => {
+ promiseChain = promiseChain.then(() => {
+ return new Promise((resolve, reject) => {
+ let promise = callback(this._itemFromObject(obj));
+ if (promise instanceof Promise) {
+ return promise.then(resolve, reject);
+ }
+ resolve();
+ return undefined;
+ });
+ });
+ }, ...optsList);
+ yield promiseChain;
+ }),
+
+ /**
+ * Returns a new ReadingListItemIterator that can be used to enumerate items
+ * in the list.
+ *
+ * @param optsList A variable number of options objects that control the
+ * items that are matched. See Options Objects.
+ * @return A new ReadingListItemIterator.
+ */
+ iterator(...optsList) {
+ let iter = new ReadingListItemIterator(this, ...optsList);
+ this._iterators.add(Cu.getWeakReference(iter));
+ return iter;
},
/**
- * Server timestamp
- * @type {string}
+ * Adds an item to the list that isn't already present.
+ *
+ * The given object represents a new item, and the properties of the object
+ * are those in ITEM_BASIC_PROPERTY_NAMES. It may have as few or as many
+ * properties that you want to set, but it must have a `url` property.
+ *
+ * It's an error to call this with an object whose `url` or `guid` properties
+ * are the same as those of items that are already present in the list. The
+ * returned promise is rejected in that case.
+ *
+ * @param obj A simple object representing an item.
+ * @return Promise Resolved with the new item when the list
+ * is updated. Rejected with an Error on error.
*/
- get lastModified() {
- return this._data.last_modified;
- },
+ addItem: Task.async(function* (obj) {
+ obj = stripNonItemProperties(obj);
+ yield this._store.addItem(obj);
+ this._invalidateIterators();
+ let item = this._itemFromObject(obj);
+ this._callListeners("onItemAdded", item);
+ return item;
+ }),
/**
- * @type {nsIURL}
+ * Updates the properties of an item that belongs to the list.
+ *
+ * The passed-in item may have as few or as many properties that you want to
+ * set; only the properties that are present are updated. The item must have
+ * a `url`, however.
+ *
+ * It's an error to call this for an item that doesn't belong to the list.
+ * The returned promise is rejected in that case.
+ *
+ * @param item The ReadingListItem to update.
+ * @return Promise Resolved when the list is updated. Rejected with an
+ * Error on error.
*/
- get originalUrl() {
- return Services.io.newURI(this._data.url, null, null);
- },
+ updateItem: Task.async(function* (item) {
+ this._ensureItemBelongsToList(item);
+ yield this._store.updateItem(item._properties);
+ this._invalidateIterators();
+ this._callListeners("onItemUpdated", item);
+ }),
/**
- * @type {string}
+ * Deletes an item from the list. The item must have a `url`.
+ *
+ * It's an error to call this for an item that doesn't belong to the list.
+ * The returned promise is rejected in that case.
+ *
+ * @param item The ReadingListItem to delete.
+ * @return Promise Resolved when the list is updated. Rejected with an
+ * Error on error.
*/
- get originalTitle() {
- return this._data.title || "";
- },
+ deleteItem: Task.async(function* (item) {
+ this._ensureItemBelongsToList(item);
+ yield this._store.deleteItemByURL(item.url);
+ item.list = null;
+ this._itemsByURL.delete(item.url);
+ this._invalidateIterators();
+ this._callListeners("onItemDeleted", item);
+ }),
/**
- * @type {nsIURL}
- */
- get resolvedUrl() {
- return Services.io.newURI(this._data.resolved_url || this._data.url, null, null);
- },
-
- /**
- * @type {string}
- */
- get resolvedTitle() {
- return this._data.resolved_title || this.originalTitle;
- },
-
- /**
- * @type {string}
- */
- get excerpt() {
- return this._data.excerpt || "";
- },
-
- /**
- * @type {ItemStates}
- */
- get state() {
- return ReadingList.ItemStates[this._data.state] || ReadingList.ItemStates.OK;
- },
-
- /**
- * @type {boolean}
- */
- get isFavorite() {
- return !!this._data.favorite;
- },
-
- /**
- * @type {boolean}
- */
- get isArticle() {
- return !!this._data.is_article;
- },
-
- /**
- * @type {number}
- */
- get wordCount() {
- return this._data.word_count || 0;
- },
-
- /**
- * @type {boolean}
- */
- get isUnread() {
- return !!this._data.unread;
- },
-
- /**
- * Device name
- * @type {string}
- */
- get addedBy() {
- return this._data.added_by;
- },
-
- /**
- * @type {Date}
- */
- get addedOn() {
- return new Date(this._data.added_on);
- },
-
- /**
- * @type {Date}
- */
- get storedOn() {
- return new Date(this._data.stored_on);
- },
-
- /**
- * Device name
- * @type {string}
- */
- get markedReadBy() {
- return this._data.marked_read_by;
- },
-
- /**
- * @type {Date}
- */
- get markedReadOn() {
- return new date(this._data.marked_read_on);
- },
-
- /**
- * @type {number}
- */
- get readPosition() {
- return this._data.read_position;
- },
-
- // Data not specified by the current server API
-
- /**
- * Array of scraped or captured summary images for this page.
- * TODO: Implement this.
- * @type {[nsIURL]}
- */
- get images() {
- return [];
- },
-
- /**
- * Favicon for this site.
- * @type {nsIURL}
- * TODO: Generate moz-anno: URI for favicon.
- */
- get favicon() {
- return null;
- },
-
- // Helpers
-
- /**
- * Alias for resolvedUrl.
- * TODO: This url/resolvedUrl alias makes it feel like the server API hasn't got this right.
- */
- get url() {
- return this.resolvedUrl;
- },
- /**
- * Alias for resolvedTitle
- */
- get title() {
- return this.resolvedTitle;
- },
-
- /**
- * Domain portion of the URL, with prefixes stripped. For display purposes.
- * @type {string}
- */
- get domain() {
- let host = this.resolvedUrl.host;
- if (host.startsWith("www.")) {
- host = host.slice(4);
- }
- return host;
- },
-
- /**
- * Convert this Item to a string representation.
- */
- toString() {
- return `[Item url=${this.url.spec}]`;
- },
-
- /**
- * Get the value that should be used for a JSON representation of this Item.
- */
- toJSON() {
- return this._data;
- },
-};
-
-
-let ItemStates = {
- OK: Symbol("ok"),
- ARCHIVED: Symbol("archived"),
- DELETED: Symbol("deleted"),
-};
-
-
-this.ReadingList = {
- Item: Item,
- ItemStates: ItemStates,
-
- _listeners: new Set(),
- _items: [],
-
- /**
- * Initialize the ReadingList component.
- */
- _init() {
- log.debug("Init");
-
- // Initialize mock data
- let mockData = JSON.parse(Preferences.get("browser.readinglist.mockData", "[]"));
- for (let itemData of mockData) {
- this._items.push(new Item(itemData));
- }
- },
-
- /**
- * Add an event listener.
- * @param {object} listener - Listener object to start notifying.
+ * Adds a listener that will be notified when the list changes. Listeners
+ * are objects with the following optional methods:
+ *
+ * onItemAdded(item)
+ * onItemUpdated(item)
+ * onItemDeleted(item)
+ *
+ * @param listener A listener object.
*/
addListener(listener) {
this._listeners.add(listener);
},
/**
- * Remove a specified event listener.
- * @param {object} listener - Listener object to stop notifying.
+ * Removes a listener from the list.
+ *
+ * @param listener A listener object.
*/
removeListener(listener) {
this._listeners.delete(listener);
},
/**
- * Notify all registered event listeners of an event.
- * @param {string} eventName - Event name, which will be used as a method name
- * on listeners to call.
+ * Call this when you're done with the list. Don't use it afterward.
*/
- _notifyListeners(eventName, ...args) {
- for (let listener of this._listeners) {
- if (typeof listener[eventName] != "function") {
- continue;
+ destroy: Task.async(function* () {
+ yield this._store.destroy();
+ for (let itemWeakRef of this._itemsByURL.values()) {
+ let item = itemWeakRef.get();
+ if (item) {
+ item.list = null;
}
+ }
+ this._itemsByURL.clear();
+ }),
- try {
- listener[eventName](...args);
- } catch (e) {
- log.error(`Error calling listener.${eventName}`, e);
+ // The list's backing store.
+ _store: null,
+
+ // A Map mapping URL strings to nsIWeakReferences that refer to
+ // ReadingListItems.
+ _itemsByURL: null,
+
+ // A Set containing nsIWeakReferences that refer to valid iterators produced
+ // by the list.
+ _iterators: null,
+
+ // A Set containing listener objects.
+ _listeners: null,
+
+ /**
+ * Returns the ReadingListItem represented by the given simple object. If
+ * the item doesn't exist yet, it's created first.
+ *
+ * @param obj A simple object with item properties.
+ * @return The ReadingListItem.
+ */
+ _itemFromObject(obj) {
+ let itemWeakRef = this._itemsByURL.get(obj.url);
+ let item = itemWeakRef ? itemWeakRef.get() : null;
+ if (item) {
+ item.setProperties(obj, false);
+ }
+ else {
+ item = new ReadingListItem(obj);
+ item.list = this;
+ this._itemsByURL.set(obj.url, Cu.getWeakReference(item));
+ }
+ return item;
+ },
+
+ /**
+ * Marks all the list's iterators as invalid, meaning it's not safe to use
+ * them anymore.
+ */
+ _invalidateIterators() {
+ for (let iterWeakRef of this._iterators) {
+ let iter = iterWeakRef.get();
+ if (iter) {
+ iter.invalidate();
+ }
+ }
+ this._iterators.clear();
+ },
+
+ /**
+ * Calls a method on all listeners.
+ *
+ * @param methodName The name of the method to call.
+ * @param item This item will be passed to the listeners.
+ */
+ _callListeners(methodName, item) {
+ for (let listener of this._listeners) {
+ if (methodName in listener) {
+ try {
+ listener[methodName](item);
+ }
+ catch (err) {
+ Cu.reportError(err);
+ }
}
}
},
+ _ensureItemBelongsToList(item) {
+ if (item.list != this) {
+ throw new Error("The item does not belong to this list");
+ }
+ },
+};
+
+/**
+ * An item in a reading list.
+ *
+ * Each item belongs to a list, and it's an error to use an item with a
+ * ReadingList that the item doesn't belong to.
+ *
+ * @param props The properties of the item, as few or many as you want.
+ */
+function ReadingListItem(props={}) {
+ this._properties = {};
+ this.setProperties(props, false);
+}
+
+ReadingListItem.prototype = {
+
/**
- * Fetch the number of items that match a set of given conditions.
- * TODO: Implement filtering, sorting, etc. Needs backend storage work.
- *
- * @param {Object} conditions Object specifying a set of conditions for
- * filtering items.
- * @return {Promise}
- * @resolves {number}
+ * Item's unique ID.
+ * @type string
*/
- getNumItems(conditions = {unread: false}) {
- return new Promise((resolve, reject) => {
- resolve(this._items.length);
- });
+ get id() {
+ if (!this._id) {
+ this._id = hash(this.url);
+ }
+ return this._id;
},
/**
- * Fetch items matching a set of conditions, in a sorted list.
- * TODO: Implement filtering, sorting, etc. Needs backend storage work.
- *
- * @return {Promise}
- * @resolves {[Item]}
+ * The item's server-side GUID. This is set by the remote server and therefore is not
+ * guarenteed to be set for local items.
+ * @type string
*/
- getItems(options = {sort: "addedOn", conditions: {unread: false}}) {
- return new Promise((resolve, reject) => {
- resolve([...this._items]);
- });
+ get guid() {
+ return this._properties.guid || undefined;
+ },
+ set guid(val) {
+ this._properties.guid = val;
+ if (this.list) {
+ this.commit();
+ }
},
/**
- * Find an item based on its ID.
- * TODO: Implement. Needs backend storage work.
- *
- * @return {Promise}
- * @resolves {Item}
+ * The date the item was last modified.
+ * @type Date
*/
- getItemByID(url) {
- return new Promise((resolve, reject) => {
- resolve(null);
- });
+ get lastModified() {
+ return this._properties.lastModified ?
+ new Date(this._properties.lastModified) :
+ undefined;
+ },
+ set lastModified(val) {
+ this._properties.lastModified = val.valueOf();
+ if (this.list) {
+ this.commit();
+ }
},
/**
- * Find an item based on its URL.
- *
- * TODO: Implement. Needs backend storage work.
- * TODO: Does this match original or resolved URL, or both?
- * TODO: Should this just be a generic findItem API?
- *
- * @return {Promise}
- * @resolves {Item}
+ * The item's URL.
+ * @type string
*/
- getItemByURL(url) {
- return new Promise((resolve, reject) => {
- resolve(null);
+ get url() {
+ return this._properties.url;
+ },
+ set url(val) {
+ this._properties.url = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's URL as an nsIURI.
+ * @type nsIURI
+ */
+ get uri() {
+ return this._properties.url ?
+ Services.io.newURI(this._properties.url, "", null) :
+ undefined;
+ },
+ set uri(val) {
+ this.url = val.spec;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * Returns the domain (a string) of the item's URL. If the URL doesn't have a
+ * domain, then the URL itself (also a string) is returned.
+ */
+ get domain() {
+ try {
+ return this.uri.host;
+ }
+ catch (err) {}
+ return this.url;
+ },
+
+ /**
+ * The item's resolved URL.
+ * @type string
+ */
+ get resolvedURL() {
+ return this._properties.resolvedURL;
+ },
+ set resolvedURL(val) {
+ this._properties.resolvedURL = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's resolved URL as an nsIURI.
+ * @type nsIURI
+ */
+ get resolvedURI() {
+ return this._properties.resolvedURL ?
+ Services.io.newURI(this._properties.resolvedURL, "", null) :
+ undefined;
+ },
+ set resolvedURI(val) {
+ this.resolvedURL = val.spec;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's title.
+ * @type string
+ */
+ get title() {
+ return this._properties.title;
+ },
+ set title(val) {
+ this._properties.title = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's resolved title.
+ * @type string
+ */
+ get resolvedTitle() {
+ return this._properties.resolvedTitle;
+ },
+ set resolvedTitle(val) {
+ this._properties.resolvedTitle = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's excerpt.
+ * @type string
+ */
+ get excerpt() {
+ return this._properties.excerpt;
+ },
+ set excerpt(val) {
+ this._properties.excerpt = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's status.
+ * @type integer
+ */
+ get status() {
+ return this._properties.status;
+ },
+ set status(val) {
+ this._properties.status = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * Whether the item is a favorite.
+ * @type boolean
+ */
+ get favorite() {
+ return !!this._properties.favorite;
+ },
+ set favorite(val) {
+ this._properties.favorite = !!val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * Whether the item is an article.
+ * @type boolean
+ */
+ get isArticle() {
+ return !!this._properties.isArticle;
+ },
+ set isArticle(val) {
+ this._properties.isArticle = !!val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's word count.
+ * @type integer
+ */
+ get wordCount() {
+ return this._properties.wordCount;
+ },
+ set wordCount(val) {
+ this._properties.wordCount = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * Whether the item is unread.
+ * @type boolean
+ */
+ get unread() {
+ return !!this._properties.unread;
+ },
+ set unread(val) {
+ this._properties.unread = !!val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The date the item was added.
+ * @type Date
+ */
+ get addedOn() {
+ return this._properties.addedOn ?
+ new Date(this._properties.addedOn) :
+ undefined;
+ },
+ set addedOn(val) {
+ this._properties.addedOn = val.valueOf();
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The date the item was stored.
+ * @type Date
+ */
+ get storedOn() {
+ return this._properties.storedOn ?
+ new Date(this._properties.storedOn) :
+ undefined;
+ },
+ set storedOn(val) {
+ this._properties.storedOn = val.valueOf();
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The GUID of the device that marked the item read.
+ * @type string
+ */
+ get markedReadBy() {
+ return this._properties.markedReadBy;
+ },
+ set markedReadBy(val) {
+ this._properties.markedReadBy = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The date the item marked read.
+ * @type Date
+ */
+ get markedReadOn() {
+ return this._properties.markedReadOn ?
+ new Date(this._properties.markedReadOn) :
+ undefined;
+ },
+ set markedReadOn(val) {
+ this._properties.markedReadOn = val.valueOf();
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * The item's read position.
+ * @param integer
+ */
+ get readPosition() {
+ return this._properties.readPosition;
+ },
+ set readPosition(val) {
+ this._properties.readPosition = val;
+ if (this.list) {
+ this.commit();
+ }
+ },
+
+ /**
+ * Sets the given properties of the item, optionally calling commit().
+ *
+ * @param props A simple object containing the properties to set.
+ * @param commit If true, commit() is called.
+ * @return Promise If commit is true, resolved when the commit
+ * completes; otherwise resolved immediately.
+ */
+ setProperties: Task.async(function* (props, commit=true) {
+ for (let name in props) {
+ this._properties[name] = props[name];
+ }
+ if (commit) {
+ yield this.commit();
+ }
+ }),
+
+ /**
+ * Deletes the item from its list.
+ *
+ * @return Promise Resolved when the list has been updated.
+ */
+ delete: Task.async(function* () {
+ this._ensureBelongsToList();
+ yield this.list.deleteItem(this);
+ this.delete = () => Promise.reject("The item has already been deleted");
+ }),
+
+ /**
+ * Notifies the item's list that the item has changed so that the list can
+ * update itself.
+ *
+ * @return Promise Resolved when the list has been updated.
+ */
+ commit: Task.async(function* () {
+ this._ensureBelongsToList();
+ yield this.list.updateItem(this);
+ }),
+
+ toJSON() {
+ return this._properties;
+ },
+
+ _ensureBelongsToList() {
+ if (!this.list) {
+ throw new Error("The item must belong to a reading list");
+ }
+ },
+};
+
+/**
+ * An object that enumerates over items in a list.
+ *
+ * You can enumerate items a chunk at a time by passing counts to forEach() and
+ * items(). An iterator remembers where it left off, so for example calling
+ * forEach() with a count of 10 will enumerate the first 10 items, and then
+ * calling it again with 10 will enumerate the next 10 items.
+ *
+ * It's possible for an iterator's list to be modified between calls to
+ * forEach() and items(). If that happens, the iterator is no longer safe to
+ * use, so it's invalidated. You can check whether an iterator is invalid by
+ * getting its `invalid` property. Attempting to use an invalid iterator will
+ * throw an error.
+ *
+ * @param list The ReadingList to enumerate.
+ * @param optsList A variable number of options objects that control the items
+ * that are matched. See Options Objects.
+ */
+function ReadingListItemIterator(list, ...optsList) {
+ this.list = list;
+ this.index = 0;
+ this.optsList = optsList;
+}
+
+ReadingListItemIterator.prototype = {
+
+ /**
+ * True if it's not safe to use the iterator. Attempting to use an invalid
+ * iterator will throw an error.
+ */
+ invalid: false,
+
+ /**
+ * Enumerates the items in the iterator starting at its current index. The
+ * iterator is advanced by the number of items enumerated.
+ *
+ * @param callback Called for each item in the enumeration. It's passed a
+ * single object, a ReadingListItem. It may return a promise; if so,
+ * the callback will not be called for the next item until the promise
+ * is resolved.
+ * @param count The maximum number of items to enumerate. Pass -1 to
+ * enumerate them all.
+ * @return Promise Resolved when the enumeration completes *and* the
+ * last promise returned by the callback is resolved.
+ */
+ forEach: Task.async(function* (callback, count=-1) {
+ this._ensureValid();
+ let optsList = clone(this.optsList);
+ optsList.push({
+ offset: this.index,
+ limit: count,
});
+ yield this.list.forEachItem(item => {
+ this.index++;
+ return callback(item);
+ }, ...optsList);
+ }),
+
+ /**
+ * Gets an array of items in the iterator starting at its current index. The
+ * iterator is advanced by the number of items fetched.
+ *
+ * @param count The maximum number of items to get.
+ * @return Promise The fetched items.
+ */
+ items: Task.async(function* (count) {
+ this._ensureValid();
+ let optsList = clone(this.optsList);
+ optsList.push({
+ offset: this.index,
+ limit: count,
+ });
+ let items = [];
+ yield this.list.forEachItem(item => items.push(item), ...optsList);
+ this.index += items.length;
+ return items;
+ }),
+
+ /**
+ * Invalidates the iterator. You probably don't want to call this unless
+ * you're a ReadingList.
+ */
+ invalidate() {
+ this.invalid = true;
+ },
+
+ _ensureValid() {
+ if (this.invalid) {
+ throw new Error("The iterator has been invalidated");
+ }
},
};
-ReadingList._init();
+function stripNonItemProperties(item) {
+ let obj = {};
+ for (let name of ITEM_BASIC_PROPERTY_NAMES) {
+ if (name in item) {
+ obj[name] = item[name];
+ }
+ }
+ return obj;
+}
+
+function hash(str) {
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.init(Ci.nsICryptoHash.MD5);
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = str;
+ hasher.updateFromStream(stream, -1);
+ let binaryStr = hasher.finish(false);
+ let hexStr =
+ [("0" + binaryStr.charCodeAt(i).toString(16)).slice(-2) for (i in hash)].
+ join("");
+ return hexStr;
+}
+
+function clone(obj) {
+ return Cu.cloneInto(obj, {}, { cloneFunctions: false });
+}
+
+
+Object.defineProperty(this, "ReadingList", {
+ get() {
+ if (!this._singleton) {
+ let store = new SQLiteStore("reading-list-temp.sqlite");
+ this._singleton = new ReadingListImpl(store);
+ }
+ return this._singleton;
+ },
+});
diff --git a/browser/components/readinglist/SQLiteStore.jsm b/browser/components/readinglist/SQLiteStore.jsm
new file mode 100644
index 000000000000..aefc8361b1b2
--- /dev/null
+++ b/browser/components/readinglist/SQLiteStore.jsm
@@ -0,0 +1,332 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "SQLiteStore",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ReadingList",
+ "resource:///modules/readinglist/ReadingList.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
+ "resource://gre/modules/Sqlite.jsm");
+
+/**
+ * A SQLite Reading List store backed by a database on disk. The database is
+ * created if it doesn't exist.
+ *
+ * @param pathRelativeToProfileDir The path of the database file relative to
+ * the profile directory.
+ */
+this.SQLiteStore = function SQLiteStore(pathRelativeToProfileDir) {
+ this.pathRelativeToProfileDir = pathRelativeToProfileDir;
+ this._ensureConnection(pathRelativeToProfileDir);
+};
+
+this.SQLiteStore.prototype = {
+
+ /**
+ * Yields the number of items in the store that match the given options.
+ *
+ * @param optsList A variable number of options objects that control the
+ * items that are matched. See Options Objects in ReadingList.jsm.
+ * @return Promise The number of matching items in the store.
+ * Rejected with an Error on error.
+ */
+ count: Task.async(function* (...optsList) {
+ let [sql, args] = sqlFromOptions(optsList);
+ let count = 0;
+ let conn = yield this._connectionPromise;
+ yield conn.executeCached(`
+ SELECT COUNT(*) AS count FROM items ${sql};
+ `, args, row => count = row.getResultByName("count"));
+ return count;
+ }),
+
+ /**
+ * Enumerates the items in the store that match the given options.
+ *
+ * @param callback Called for each item in the enumeration. It's passed a
+ * single object, an item.
+ * @param optsList A variable number of options objects that control the
+ * items that are matched. See Options Objects in ReadingList.jsm.
+ * @return Promise Resolved when the enumeration completes. Rejected
+ * with an Error on error.
+ */
+ forEachItem: Task.async(function* (callback, ...optsList) {
+ let [sql, args] = sqlFromOptions(optsList);
+ let colNames = ReadingList.ItemBasicPropertyNames;
+ let conn = yield this._connectionPromise;
+ yield conn.executeCached(`
+ SELECT ${colNames} FROM items ${sql};
+ `, args, row => callback(itemFromRow(row)));
+ }),
+
+ /**
+ * Adds an item to the store that isn't already present. See
+ * ReadingList.prototype.addItems.
+ *
+ * @param items A simple object representing an item.
+ * @return Promise Resolved when the store is updated. Rejected with an
+ * Error on error.
+ */
+ addItem: Task.async(function* (item) {
+ let colNames = [];
+ let paramNames = [];
+ for (let propName in item) {
+ colNames.push(propName);
+ paramNames.push(`:${propName}`);
+ }
+ let conn = yield this._connectionPromise;
+ yield conn.executeCached(`
+ INSERT INTO items (${colNames}) VALUES (${paramNames});
+ `, item);
+ }),
+
+ /**
+ * Updates the properties of an item that's already present in the store. See
+ * ReadingList.prototype.updateItem.
+ *
+ * @param item The item to update. It must have a `url`.
+ * @return Promise Resolved when the store is updated. Rejected with an
+ * Error on error.
+ */
+ updateItem: Task.async(function* (item) {
+ let assignments = [];
+ for (let propName in item) {
+ assignments.push(`${propName} = :${propName}`);
+ }
+ let conn = yield this._connectionPromise;
+ yield conn.executeCached(`
+ UPDATE items SET ${assignments} WHERE url = :url;
+ `, item);
+ }),
+
+ /**
+ * Deletes an item from the store.
+ *
+ * @param url The URL string of the item to delete.
+ * @return Promise Resolved when the store is updated. Rejected with an
+ * Error on error.
+ */
+ deleteItemByURL: Task.async(function* (url) {
+ let conn = yield this._connectionPromise;
+ yield conn.executeCached(`
+ DELETE FROM items WHERE url = :url;
+ `, { url: url });
+ }),
+
+ /**
+ * Call this when you're done with the store. Don't use it afterward.
+ */
+ destroy: Task.async(function* () {
+ let conn = yield this._connectionPromise;
+ yield conn.close();
+ this._connectionPromise = Promise.reject("Store destroyed");
+ }),
+
+ /**
+ * Creates the database connection if it hasn't been created already.
+ *
+ * @param pathRelativeToProfileDir The path of the database file relative to
+ * the profile directory.
+ */
+ _ensureConnection: Task.async(function* (pathRelativeToProfileDir) {
+ if (!this._connectionPromise) {
+ this._connectionPromise = Task.spawn(function* () {
+ let conn = yield Sqlite.openConnection({
+ path: pathRelativeToProfileDir,
+ sharedMemoryCache: false,
+ });
+ Sqlite.shutdown.addBlocker("readinglist/SQLiteStore: Destroy",
+ this.destroy.bind(this));
+ yield conn.execute(`
+ PRAGMA locking_mode = EXCLUSIVE;
+ `);
+ yield this._checkSchema(conn);
+ return conn;
+ }.bind(this));
+ }
+ }),
+
+ // Promise
+ _connectionPromise: null,
+
+ // The current schema version.
+ _schemaVersion: 1,
+
+ _checkSchema: Task.async(function* (conn) {
+ let version = parseInt(yield conn.getSchemaVersion());
+ for (; version < this._schemaVersion; version++) {
+ let meth = `_migrateSchema${version}To${version + 1}`;
+ yield this[meth](conn);
+ }
+ yield conn.setSchemaVersion(this._schemaVersion);
+ }),
+
+ _migrateSchema0To1: Task.async(function* (conn) {
+ yield conn.execute(`
+ PRAGMA journal_mode = wal;
+ `);
+ // 524288 bytes = 512 KiB
+ yield conn.execute(`
+ PRAGMA journal_size_limit = 524288;
+ `);
+ yield conn.execute(`
+ CREATE TABLE items (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ guid TEXT UNIQUE,
+ url TEXT NOT NULL UNIQUE,
+ resolvedURL TEXT UNIQUE,
+ lastModified INTEGER,
+ title TEXT,
+ resolvedTitle TEXT,
+ excerpt TEXT,
+ status INTEGER,
+ favorite BOOLEAN,
+ isArticle BOOLEAN,
+ wordCount INTEGER,
+ unread BOOLEAN,
+ addedBy TEXT,
+ addedOn INTEGER,
+ storedOn INTEGER,
+ markedReadBy TEXT,
+ markedReadOn INTEGER,
+ readPosition INTEGER
+ );
+ `);
+ yield conn.execute(`
+ CREATE INDEX items_addedOn ON items (addedOn);
+ `);
+ yield conn.execute(`
+ CREATE INDEX items_unread ON items (unread);
+ `);
+ }),
+};
+
+/**
+ * Returns a simple object whose properties are the
+ * ReadingList.ItemBasicPropertyNames properties lifted from the given row.
+ *
+ * @param row A mozIStorageRow.
+ * @return The item.
+ */
+function itemFromRow(row) {
+ let item = {};
+ for (let name of ReadingList.ItemBasicPropertyNames) {
+ item[name] = row.getResultByName(name);
+ }
+ return item;
+}
+
+/**
+ * Returns the back part of a SELECT statement generated from the given list of
+ * options.
+ *
+ * @param optsList See Options Objects in ReadingList.jsm.
+ * @return An array [sql, args]. sql is a string of SQL. args is an object
+ * that contains arguments for all the parameters in sql.
+ */
+function sqlFromOptions(optsList) {
+ // We modify the options objects, which were passed in by the store client, so
+ // clone them first.
+ optsList = Cu.cloneInto(optsList, {}, { cloneFunctions: false });
+
+ let sort;
+ let sortDir;
+ let limit;
+ let offset;
+ for (let opts of optsList) {
+ if ("sort" in opts) {
+ sort = opts.sort;
+ delete opts.sort;
+ }
+ if ("descending" in opts) {
+ if (opts.descending) {
+ sortDir = "DESC";
+ }
+ delete opts.descending;
+ }
+ if ("limit" in opts) {
+ limit = opts.limit;
+ delete opts.limit;
+ }
+ if ("offset" in opts) {
+ offset = opts.offset;
+ delete opts.offset;
+ }
+ }
+
+ let fragments = [];
+
+ if (sort) {
+ sortDir = sortDir || "ASC";
+ fragments.push(`ORDER BY ${sort} ${sortDir}`);
+ }
+ if (limit) {
+ fragments.push(`LIMIT ${limit}`);
+ if (offset) {
+ fragments.push(`OFFSET ${offset}`);
+ }
+ }
+
+ let args = {};
+
+ function uniqueParamName(name) {
+ if (name in args) {
+ for (let i = 1; ; i++) {
+ let newName = `${name}_${i}`;
+ if (!(newName in args)) {
+ return newName;
+ }
+ }
+ }
+ return name;
+ }
+
+ // Build a WHERE clause for the remaining properties. Assume they all refer
+ // to columns. (If they don't, the SQL query will fail.)
+ let disjunctions = [];
+ for (let opts of optsList) {
+ let conjunctions = [];
+ for (let key in opts) {
+ if (Array.isArray(opts[key])) {
+ // Convert arrays to IN expressions. e.g., { guid: ['a', 'b', 'c'] }
+ // becomes "guid IN (:guid, :guid_1, :guid_2)". The guid_i arguments
+ // are added to opts.
+ let array = opts[key];
+ let params = [];
+ for (let i = 0; i < array.length; i++) {
+ let paramName = uniqueParamName(key);
+ params.push(`:${paramName}`);
+ args[paramName] = array[i];
+ }
+ conjunctions.push(`${key} IN (${params})`);
+ }
+ else {
+ let paramName = uniqueParamName(key);
+ conjunctions.push(`${key} = :${paramName}`);
+ args[paramName] = opts[key];
+ }
+ }
+ let conjunction = conjunctions.join(" AND ");
+ if (conjunction) {
+ disjunctions.push(`(${conjunction})`);
+ }
+ }
+ let disjunction = disjunctions.join(" OR ");
+ if (disjunction) {
+ let where = `WHERE ${disjunction}`;
+ fragments = [where].concat(fragments);
+ }
+
+ let sql = fragments.join(" ");
+ return [sql, args];
+}
diff --git a/browser/components/readinglist/moz.build b/browser/components/readinglist/moz.build
index b02a45bf7d72..e29406e642df 100644
--- a/browser/components/readinglist/moz.build
+++ b/browser/components/readinglist/moz.build
@@ -6,6 +6,7 @@ JAR_MANIFESTS += ['jar.mn']
EXTRA_JS_MODULES.readinglist += [
'ReadingList.jsm',
+ 'SQLiteStore.jsm',
]
TESTING_JS_MODULES += [
diff --git a/browser/components/readinglist/sidebar.js b/browser/components/readinglist/sidebar.js
index 249a6707d3dd..d80b834e1e44 100644
--- a/browser/components/readinglist/sidebar.js
+++ b/browser/components/readinglist/sidebar.js
@@ -7,6 +7,7 @@
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/readinglist/ReadingList.jsm");
@@ -21,6 +22,12 @@ let RLSidebar = {
*/
list: null,
+ /**
+ * A promise that's resolved when building the initial list completes.
+ * @type {Promise}
+ */
+ listPromise: null,
+
/**
* element used for constructing list item elements.
* @type {Element}
@@ -53,7 +60,7 @@ let RLSidebar = {
this.list.addEventListener("mousemove", event => this.onListMouseMove(event));
this.list.addEventListener("keydown", event => this.onListKeyDown(event), true);
- this.ensureListItems();
+ this.listPromise = this.ensureListItems();
ReadingList.addListener(this);
let initEvent = new CustomEvent("Initialized", {bubbles: true});
@@ -74,7 +81,7 @@ let RLSidebar = {
* TODO: We may not want to show this new item right now.
* TODO: We should guard against the list growing here.
*
- * @param {Readinglist.Item} item - Item that was added.
+ * @param {ReadinglistItem} item - Item that was added.
*/
onItemAdded(item) {
log.trace(`onItemAdded: ${item}`);
@@ -88,7 +95,7 @@ let RLSidebar = {
/**
* Handle an item being deleted from the ReadingList.
- * @param {ReadingList.Item} item - Item that was deleted.
+ * @param {ReadingListItem} item - Item that was deleted.
*/
onItemDeleted(item) {
log.trace(`onItemDeleted: ${item}`);
@@ -103,7 +110,7 @@ let RLSidebar = {
/**
* Handle an item in the ReadingList having any of its properties changed.
- * @param {ReadingList.Item} item - Item that was updated.
+ * @param {ReadingListItem} item - Item that was updated.
*/
onItemUpdated(item) {
log.trace(`onItemUpdated: ${item}`);
@@ -118,12 +125,12 @@ let RLSidebar = {
/**
* Update the element representing an item, ensuring it's in sync with the
* underlying data.
- * @param {ReadingList.Item} item - Item to use as a source.
+ * @param {ReadingListItem} item - Item to use as a source.
* @param {Element} itemNode - Element to update.
*/
updateItem(item, itemNode) {
itemNode.setAttribute("id", "item-" + item.id);
- itemNode.setAttribute("title", `${item.title}\n${item.url.spec}`);
+ itemNode.setAttribute("title", `${item.title}\n${item.url}`);
itemNode.querySelector(".item-title").textContent = item.title;
itemNode.querySelector(".item-domain").textContent = item.domain;
@@ -132,18 +139,16 @@ let RLSidebar = {
/**
* Ensure that the list is populated with the correct items.
*/
- ensureListItems() {
- ReadingList.getItems().then(items => {
- for (let item of items) {
- // TODO: Should be batch inserting via DocumentFragment
- try {
- this.onItemAdded(item);
- } catch (e) {
- log.warn("Error adding item", e);
- }
+ ensureListItems: Task.async(function* () {
+ yield ReadingList.forEachItem(item => {
+ // TODO: Should be batch inserting via DocumentFragment
+ try {
+ this.onItemAdded(item);
+ } catch (e) {
+ log.warn("Error adding item", e);
}
});
- },
+ }),
/**
* Get the number of items currently displayed in the list.
@@ -317,7 +322,7 @@ let RLSidebar = {
}
let item = this.getItemFromNode(itemNode);
- this.openURL(item.url.spec, event);
+ this.openURL(item.url, event);
},
/**
diff --git a/browser/components/readinglist/test/ReadingListTestUtils.jsm b/browser/components/readinglist/test/ReadingListTestUtils.jsm
index 775b6e80fe8a..30c91337dd1b 100644
--- a/browser/components/readinglist/test/ReadingListTestUtils.jsm
+++ b/browser/components/readinglist/test/ReadingListTestUtils.jsm
@@ -7,6 +7,7 @@ this.EXPORTED_SYMBOLS = [
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource:///modules/readinglist/ReadingList.jsm");
@@ -41,6 +42,15 @@ SidebarUtils.prototype = {
return this.RLSidebar.list;
},
+ /**
+ * Opens the sidebar and waits until it finishes building its list.
+ * @return {Promise} Resolved when the sidebar's list is ready.
+ */
+ showSidebar: Task.async(function* () {
+ yield this.window.ReadingListUI.showSidebar();
+ yield this.RLSidebar.listPromise;
+ }),
+
/**
* Check that the number of elements in the list matches the expected count.
* @param {number} count - Expected number of items.
@@ -136,24 +146,18 @@ this.ReadingListTestUtils = {
}
return Promise.all(promises);
}
-
- return new Promise(resolve => {
- let item = new ReadingList.Item(data);
- ReadingList._items.push(item);
- ReadingList._notifyListeners("onItemAdded", item);
- resolve(item);
- });
+ return ReadingList.addItem(data);
},
/**
* Cleanup all data, resetting to a blank state.
*/
- cleanup() {
- return new Promise(resolve => {
- ReadingList._items = [];
- ReadingList._listeners.clear();
- Preferences.reset(PREF_RL_ENABLED);
- resolve();
- });
- },
+ cleanup: Task.async(function *() {
+ Preferences.reset(PREF_RL_ENABLED);
+ let items = [];
+ yield ReadingList.forEachItem(i => items.push(i));
+ for (let item of items) {
+ yield ReadingList.deleteItem(item);
+ }
+ }),
};
diff --git a/browser/components/readinglist/test/browser/browser_sidebar_list.js b/browser/components/readinglist/test/browser/browser_sidebar_list.js
index 8826b7c9b6d4..b661e87a87a7 100644
--- a/browser/components/readinglist/test/browser/browser_sidebar_list.js
+++ b/browser/components/readinglist/test/browser/browser_sidebar_list.js
@@ -10,7 +10,7 @@ add_task(function*() {
RLUtils.enabled = true;
- yield ReadingListUI.showSidebar();
+ yield RLSidebarUtils.showSidebar();
let RLSidebar = RLSidebarUtils.RLSidebar;
let sidebarDoc = SidebarUI.browser.contentDocument;
Assert.equal(RLSidebar.numItems, 0, "Should start with no items");
@@ -19,7 +19,6 @@ add_task(function*() {
info("Adding first item");
yield RLUtils.addItem({
- id: "c3502a49-bcef-4a94-b222-d4834463de33",
url: "http://example.com/article1",
title: "Article 1",
});
@@ -27,11 +26,9 @@ add_task(function*() {
info("Adding more items");
yield RLUtils.addItem([{
- id: "e054f5b7-1f4f-463f-bb96-d64c02448c31",
url: "http://example.com/article2",
title: "Article 2",
}, {
- id: "4207230b-2364-4e97-9587-01312b0ce4e6",
url: "http://example.com/article3",
title: "Article 3",
}]);
@@ -42,12 +39,11 @@ add_task(function*() {
info("Adding another item");
yield RLUtils.addItem({
- id: "dae0e855-607e-4df3-b27f-73a5e35c94fe",
url: "http://example.com/article4",
title: "Article 4",
});
- info("Re-eopning sidebar");
- yield ReadingListUI.showSidebar();
+ info("Re-opening sidebar");
+ yield RLSidebarUtils.showSidebar();
RLSidebarUtils.expectNumItems(4);
});
diff --git a/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js b/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js
index ddcb1cb74208..4fbdffa31aa5 100644
--- a/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js
+++ b/browser/components/readinglist/test/browser/browser_sidebar_mouse_nav.js
@@ -23,62 +23,60 @@ add_task(function*() {
RLUtils.enabled = true;
let itemData = [{
- id: "00bd24c7-3629-40b0-acde-37aa81768735",
url: "http://example.com/article1",
title: "Article 1",
}, {
- id: "28bf7f19-cf94-4ceb-876a-ac1878342e0d",
url: "http://example.com/article2",
title: "Article 2",
}, {
- id: "7e5064ea-f45d-4fc7-8d8c-c067b7781e78",
url: "http://example.com/article3",
title: "Article 3",
}, {
- id: "8e72a472-8db8-4904-ba39-9672f029e2d0",
url: "http://example.com/article4",
title: "Article 4",
}, {
- id: "8d332744-37bc-4a1a-a26b-e9953b9f7d91",
url: "http://example.com/article5",
title: "Article 5",
}];
info("Adding initial mock data");
yield RLUtils.addItem(itemData);
+ info("Fetching items");
+ let items = yield ReadingList.iterator({ sort: "url" }).items(itemData.length);
+
info("Opening sidebar");
- yield ReadingListUI.showSidebar();
+ yield RLSidebarUtils.showSidebar();
RLSidebarUtils.expectNumItems(5);
RLSidebarUtils.expectSelectedId(null);
RLSidebarUtils.expectActiveId(null);
info("Mouse move over item 1");
yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[0]);
- RLSidebarUtils.expectSelectedId(itemData[0].id);
+ RLSidebarUtils.expectSelectedId(items[0].id);
RLSidebarUtils.expectActiveId(null);
info("Mouse move over item 2");
yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[1]);
- RLSidebarUtils.expectSelectedId(itemData[1].id);
+ RLSidebarUtils.expectSelectedId(items[1].id);
RLSidebarUtils.expectActiveId(null);
info("Mouse move over item 5");
yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[4]);
- RLSidebarUtils.expectSelectedId(itemData[4].id);
+ RLSidebarUtils.expectSelectedId(items[4].id);
RLSidebarUtils.expectActiveId(null);
info("Mouse move over item 1 again");
yield mouseInteraction("mousemove", "SelectedItemChanged", RLSidebarUtils.list.children[0]);
- RLSidebarUtils.expectSelectedId(itemData[0].id);
+ RLSidebarUtils.expectSelectedId(items[0].id);
RLSidebarUtils.expectActiveId(null);
info("Mouse click on item 1");
yield mouseInteraction("click", "ActiveItemChanged", RLSidebarUtils.list.children[0]);
- RLSidebarUtils.expectSelectedId(itemData[0].id);
- RLSidebarUtils.expectActiveId(itemData[0].id);
+ RLSidebarUtils.expectSelectedId(items[0].id);
+ RLSidebarUtils.expectActiveId(items[0].id);
info("Mouse click on item 3");
yield mouseInteraction("click", "ActiveItemChanged", RLSidebarUtils.list.children[2]);
- RLSidebarUtils.expectSelectedId(itemData[2].id);
- RLSidebarUtils.expectActiveId(itemData[2].id);
+ RLSidebarUtils.expectSelectedId(items[2].id);
+ RLSidebarUtils.expectActiveId(items[2].id);
});
diff --git a/browser/components/readinglist/test/xpcshell/test_ReadingList.js b/browser/components/readinglist/test/xpcshell/test_ReadingList.js
new file mode 100644
index 000000000000..092cee719e9a
--- /dev/null
+++ b/browser/components/readinglist/test/xpcshell/test_ReadingList.js
@@ -0,0 +1,701 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gDBFile = do_get_profile();
+
+Cu.import("resource:///modules/readinglist/ReadingList.jsm");
+Cu.import("resource:///modules/readinglist/SQLiteStore.jsm");
+Cu.import("resource://gre/modules/Sqlite.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+var gList;
+var gItems;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* prepare() {
+ gList = ReadingList;
+ Assert.ok(gList);
+ gDBFile.append(gList._store.pathRelativeToProfileDir);
+ do_register_cleanup(() => {
+ if (gDBFile.exists()) {
+ gDBFile.remove(true);
+ }
+ });
+
+ gItems = [];
+ for (let i = 0; i < 3; i++) {
+ gItems.push({
+ list: gList,
+ guid: `guid${i}`,
+ url: `http://example.com/${i}`,
+ resolvedURL: `http://example.com/resolved/${i}`,
+ title: `title ${i}`,
+ excerpt: `excerpt ${i}`,
+ unread: 0,
+ addedOn: Date.now(),
+ lastModified: Date.now(),
+ favorite: 0,
+ isArticle: 1,
+ storedOn: Date.now(),
+ });
+ }
+
+ for (let item of gItems) {
+ let addedItem = yield gList.addItem(item);
+ checkItems(addedItem, item);
+ }
+});
+
+add_task(function* item_properties() {
+ // get an item
+ let iter = gList.iterator({
+ sort: "guid",
+ });
+ let item = (yield iter.items(1))[0];
+ Assert.ok(item);
+
+ Assert.ok(item.uri);
+ Assert.ok(item.uri instanceof Ci.nsIURI);
+ Assert.equal(item.uri.spec, item.url);
+
+ Assert.ok(item.resolvedURI);
+ Assert.ok(item.resolvedURI instanceof Ci.nsIURI);
+ Assert.equal(item.resolvedURI.spec, item.resolvedURL);
+
+ Assert.ok(item.lastModified);
+ Assert.ok(item.lastModified instanceof Cu.getGlobalForObject(ReadingList).Date);
+
+ Assert.ok(item.addedOn);
+ Assert.ok(item.addedOn instanceof Cu.getGlobalForObject(ReadingList).Date);
+
+ Assert.ok(item.storedOn);
+ Assert.ok(item.storedOn instanceof Cu.getGlobalForObject(ReadingList).Date);
+
+ Assert.ok(typeof(item.favorite) == "boolean");
+ Assert.ok(typeof(item.isArticle) == "boolean");
+ Assert.ok(typeof(item.unread) == "boolean");
+
+ Assert.equal(item.domain, "example.com");
+ Assert.equal(item.id, hash(item.url));
+});
+
+add_task(function* constraints() {
+ // add an item again
+ let err = null;
+ try {
+ yield gList.addItem(gItems[0]);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+
+ // add a new item with an existing guid
+ let item = kindOfClone(gItems[0]);
+ item.guid = gItems[0].guid;
+ err = null;
+ try {
+ yield gList.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+
+ // add a new item with an existing url
+ item = kindOfClone(gItems[0]);
+ item.url = gItems[0].url;
+ err = null;
+ try {
+ yield gList.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+
+ // update an item with an existing url
+ item.guid = gItems[1].guid;
+ err = null;
+ try {
+ yield gList.updateItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+
+ // add a new item with an existing resolvedURL
+ item = kindOfClone(gItems[0]);
+ item.resolvedURL = gItems[0].resolvedURL;
+ err = null;
+ try {
+ yield gList.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+
+ // update an item with an existing resolvedURL
+ item.url = gItems[1].url;
+ err = null;
+ try {
+ yield gList.updateItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+
+ // add a new item with no guid, which is allowed
+ item = kindOfClone(gItems[0]);
+ delete item.guid;
+ err = null;
+ try {
+ yield gList.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ Assert.ok(!err, err ? err.message : undefined);
+ let item1 = item;
+
+ // add a second item with no guid, which is allowed
+ item = kindOfClone(gItems[1]);
+ delete item.guid;
+ err = null;
+ try {
+ yield gList.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ Assert.ok(!err, err ? err.message : undefined);
+ let item2 = item;
+
+ // Delete both items since other tests assume the store contains only gItems.
+ item1.list = gList;
+ item2.list = gList;
+ yield gList.deleteItem(item1);
+ yield gList.deleteItem(item2);
+ let items = [];
+ yield gList.forEachItem(i => items.push(i), { url: [item1.url, item2.url] });
+ Assert.equal(items.length, 0);
+
+ // add a new item with no url
+ item = kindOfClone(gItems[0]);
+ delete item.url;
+ err = null;
+ try {
+ yield gList.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err);
+});
+
+add_task(function* count() {
+ let count = yield gList.count();
+ Assert.equal(count, gItems.length);
+
+ count = yield gList.count({
+ guid: gItems[0].guid,
+ });
+ Assert.equal(count, 1);
+});
+
+add_task(function* forEachItem() {
+ // all items
+ let items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ sort: "guid",
+ });
+ checkItems(items, gItems);
+
+ // first item
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ limit: 1,
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(0, 1));
+
+ // last item
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ limit: 1,
+ sort: "guid",
+ descending: true,
+ });
+ checkItems(items, gItems.slice(gItems.length - 1, gItems.length));
+
+ // match on a scalar property
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ guid: gItems[0].guid,
+ });
+ checkItems(items, gItems.slice(0, 1));
+
+ // match on an array
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ guid: gItems.map(i => i.guid),
+ sort: "guid",
+ });
+ checkItems(items, gItems);
+
+ // match on AND'ed properties
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ guid: gItems.map(i => i.guid),
+ title: gItems[0].title,
+ sort: "guid",
+ });
+ checkItems(items, [gItems[0]]);
+
+ // match on OR'ed properties
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ guid: gItems[1].guid,
+ sort: "guid",
+ }, {
+ guid: gItems[0].guid,
+ });
+ checkItems(items, [gItems[0], gItems[1]]);
+
+ // match on AND'ed and OR'ed properties
+ items = [];
+ yield gList.forEachItem(item => items.push(item), {
+ guid: gItems.map(i => i.guid),
+ title: gItems[1].title,
+ sort: "guid",
+ }, {
+ guid: gItems[0].guid,
+ });
+ checkItems(items, [gItems[0], gItems[1]]);
+});
+
+add_task(function* forEachItem_promises() {
+ // promises resolved immediately
+ let items = [];
+ yield gList.forEachItem(item => {
+ items.push(item);
+ return Promise.resolve();
+ }, {
+ sort: "guid",
+ });
+ checkItems(items, gItems);
+
+ // promises resolved after a delay
+ items = [];
+ let i = 0;
+ let promises = [];
+ yield gList.forEachItem(item => {
+ items.push(item);
+ // The previous promise should have been resolved by now.
+ if (i > 0) {
+ Assert.equal(promises[i - 1], null);
+ }
+ // Make a new promise that should continue iteration when resolved.
+ let this_i = i++;
+ let promise = new Promise(resolve => {
+ // Resolve the promise one second from now. The idea is that if
+ // forEachItem works correctly, then the callback should not be called
+ // again before the promise resolves -- before one second elapases.
+ // Maybe there's a better way to do this that doesn't hinge on timeouts.
+ setTimeout(() => {
+ promises[this_i] = null;
+ resolve();
+ }, 0);
+ });
+ promises.push(promise);
+ return promise;
+ }, {
+ sort: "guid",
+ });
+ checkItems(items, gItems);
+});
+
+add_task(function* iterator_forEach() {
+ // no limit
+ let items = [];
+ let iter = gList.iterator({
+ sort: "guid",
+ });
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, gItems);
+
+ // limit one each time
+ items = [];
+ iter = gList.iterator({
+ sort: "guid",
+ });
+ for (let i = 0; i < gItems.length; i++) {
+ yield iter.forEach(item => items.push(item), 1);
+ checkItems(items, gItems.slice(0, i + 1));
+ }
+ yield iter.forEach(item => items.push(item), 100);
+ checkItems(items, gItems);
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, gItems);
+
+ // match on a scalar property
+ items = [];
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems[0].guid,
+ });
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, [gItems[0]]);
+
+ // match on an array
+ items = [];
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems.map(i => i.guid),
+ });
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, gItems);
+
+ // match on AND'ed properties
+ items = [];
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems.map(i => i.guid),
+ title: gItems[0].title,
+ });
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, [gItems[0]]);
+
+ // match on OR'ed properties
+ items = [];
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems[1].guid,
+ }, {
+ guid: gItems[0].guid,
+ });
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, [gItems[0], gItems[1]]);
+
+ // match on AND'ed and OR'ed properties
+ items = [];
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems.map(i => i.guid),
+ title: gItems[1].title,
+ }, {
+ guid: gItems[0].guid,
+ });
+ yield iter.forEach(item => items.push(item));
+ checkItems(items, [gItems[0], gItems[1]]);
+});
+
+add_task(function* iterator_items() {
+ // no limit
+ let iter = gList.iterator({
+ sort: "guid",
+ });
+ let items = yield iter.items(gItems.length);
+ checkItems(items, gItems);
+ items = yield iter.items(100);
+ checkItems(items, []);
+
+ // limit one each time
+ iter = gList.iterator({
+ sort: "guid",
+ });
+ for (let i = 0; i < gItems.length; i++) {
+ items = yield iter.items(1);
+ checkItems(items, gItems.slice(i, i + 1));
+ }
+ items = yield iter.items(100);
+ checkItems(items, []);
+
+ // match on a scalar property
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems[0].guid,
+ });
+ items = yield iter.items(gItems.length);
+ checkItems(items, [gItems[0]]);
+
+ // match on an array
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems.map(i => i.guid),
+ });
+ items = yield iter.items(gItems.length);
+ checkItems(items, gItems);
+
+ // match on AND'ed properties
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems.map(i => i.guid),
+ title: gItems[0].title,
+ });
+ items = yield iter.items(gItems.length);
+ checkItems(items, [gItems[0]]);
+
+ // match on OR'ed properties
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems[1].guid,
+ }, {
+ guid: gItems[0].guid,
+ });
+ items = yield iter.items(gItems.length);
+ checkItems(items, [gItems[0], gItems[1]]);
+
+ // match on AND'ed and OR'ed properties
+ iter = gList.iterator({
+ sort: "guid",
+ guid: gItems.map(i => i.guid),
+ title: gItems[1].title,
+ }, {
+ guid: gItems[0].guid,
+ });
+ items = yield iter.items(gItems.length);
+ checkItems(items, [gItems[0], gItems[1]]);
+});
+
+add_task(function* iterator_forEach_promise() {
+ // promises resolved immediately
+ let items = [];
+ let iter = gList.iterator({
+ sort: "guid",
+ });
+ yield iter.forEach(item => {
+ items.push(item);
+ return Promise.resolve();
+ });
+ checkItems(items, gItems);
+
+ // promises resolved after a delay
+ // See forEachItem_promises above for comments on this part.
+ items = [];
+ let i = 0;
+ let promises = [];
+ iter = gList.iterator({
+ sort: "guid",
+ });
+ yield iter.forEach(item => {
+ items.push(item);
+ if (i > 0) {
+ Assert.equal(promises[i - 1], null);
+ }
+ let this_i = i++;
+ let promise = new Promise(resolve => {
+ setTimeout(() => {
+ promises[this_i] = null;
+ resolve();
+ }, 0);
+ });
+ promises.push(promise);
+ return promise;
+ });
+ checkItems(items, gItems);
+});
+
+add_task(function* updateItem() {
+ // get an item
+ let items = [];
+ yield gList.forEachItem(i => items.push(i), {
+ guid: gItems[0].guid,
+ });
+ Assert.equal(items.length, 1);
+ let item = {
+ _properties: items[0]._properties,
+ list: items[0].list,
+ };
+
+ // update its title
+ let newTitle = "updateItem new title";
+ Assert.notEqual(item.title, newTitle);
+ item._properties.title = newTitle;
+ yield gList.updateItem(item);
+
+ // get the item again
+ items = [];
+ yield gList.forEachItem(i => items.push(i), {
+ guid: gItems[0].guid,
+ });
+ Assert.equal(items.length, 1);
+ item = items[0];
+ Assert.equal(item.title, newTitle);
+});
+
+add_task(function* item_setProperties() {
+ // get an item
+ let iter = gList.iterator({
+ sort: "guid",
+ });
+ let item = (yield iter.items(1))[0];
+ Assert.ok(item);
+
+ // item.setProperties(commit=false). After fetching the item again, its title
+ // should be the old title.
+ let oldTitle = item.title;
+ let newTitle = "item_setProperties title 1";
+ Assert.notEqual(oldTitle, newTitle);
+ item.setProperties({ title: newTitle }, false);
+ Assert.equal(item.title, newTitle);
+ iter = gList.iterator({
+ sort: "guid",
+ });
+ let sameItem = (yield iter.items(1))[0];
+ Assert.ok(item === sameItem);
+ Assert.equal(sameItem.title, oldTitle);
+
+ // item.setProperties(commit=true). After fetching the item again, its title
+ // should be the new title.
+ newTitle = "item_setProperties title 2";
+ item.setProperties({ title: newTitle }, true);
+ Assert.equal(item.title, newTitle);
+ iter = gList.iterator({
+ sort: "guid",
+ });
+ sameItem = (yield iter.items(1))[0];
+ Assert.ok(item === sameItem);
+ Assert.equal(sameItem.title, newTitle);
+
+ // Set item.title directly. After fetching the item again, its title should
+ // be the new title.
+ newTitle = "item_setProperties title 3";
+ item.title = newTitle;
+ Assert.equal(item.title, newTitle);
+ iter = gList.iterator({
+ sort: "guid",
+ });
+ sameItem = (yield iter.items(1))[0];
+ Assert.ok(item === sameItem);
+ Assert.equal(sameItem.title, newTitle);
+});
+
+add_task(function* listeners() {
+ // add an item
+ let resolve;
+ let listenerPromise = new Promise(r => resolve = r);
+ let listener = {
+ onItemAdded: resolve,
+ };
+ gList.addListener(listener);
+ let item = kindOfClone(gItems[0]);
+ let items = yield Promise.all([listenerPromise, gList.addItem(item)]);
+ Assert.ok(items[0]);
+ Assert.ok(items[0] === items[1]);
+ gList.removeListener(listener);
+
+ // update an item
+ listenerPromise = new Promise(r => resolve = r);
+ listener = {
+ onItemUpdated: resolve,
+ };
+ gList.addListener(listener);
+ items[0].title = "listeners new title";
+ let listenerItem = yield listenerPromise;
+ Assert.ok(listenerItem);
+ Assert.ok(listenerItem === items[0]);
+ gList.removeListener(listener);
+
+ // delete an item
+ listenerPromise = new Promise(r => resolve = r);
+ listener = {
+ onItemDeleted: resolve,
+ };
+ gList.addListener(listener);
+ items[0].delete();
+ listenerItem = yield listenerPromise;
+ Assert.ok(listenerItem);
+ Assert.ok(listenerItem === items[0]);
+ gList.removeListener(listener);
+});
+
+// This test deletes items so it should probably run last.
+add_task(function* deleteItem() {
+ // delete first item with item.delete()
+ let iter = gList.iterator({
+ sort: "guid",
+ });
+ let item = (yield iter.items(1))[0];
+ Assert.ok(item);
+ item.delete();
+ gItems[0].list = null;
+ Assert.equal((yield gList.count()), gItems.length - 1);
+ let items = [];
+ yield gList.forEachItem(i => items.push(i), {
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(1));
+
+ // delete second item with list.deleteItem()
+ yield gList.deleteItem(gItems[1]);
+ gItems[1].list = null;
+ Assert.equal((yield gList.count()), gItems.length - 2);
+ items = [];
+ yield gList.forEachItem(i => items.push(i), {
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(2));
+
+ // delete third item with list.deleteItem()
+ yield gList.deleteItem(gItems[2]);
+ gItems[2].list = null;
+ Assert.equal((yield gList.count()), gItems.length - 3);
+ items = [];
+ yield gList.forEachItem(i => items.push(i), {
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(3));
+});
+
+function checkItems(actualItems, expectedItems) {
+ Assert.equal(actualItems.length, expectedItems.length);
+ for (let i = 0; i < expectedItems.length; i++) {
+ for (let prop in expectedItems[i]) {
+ if (prop != "list") {
+ Assert.ok(prop in actualItems[i]._properties, prop);
+ Assert.equal(actualItems[i]._properties[prop], expectedItems[i][prop]);
+ }
+ }
+ Assert.equal(actualItems[i].list, expectedItems[i].list);
+ }
+}
+
+function checkError(err) {
+ Assert.ok(err);
+ Assert.ok(err instanceof Cu.getGlobalForObject(Sqlite).Error);
+}
+
+function kindOfClone(item) {
+ let newItem = {};
+ for (let prop in item) {
+ newItem[prop] = item[prop];
+ if (typeof(newItem[prop]) == "string") {
+ newItem[prop] += " -- make this string different";
+ }
+ }
+ return newItem;
+}
+
+function hash(str) {
+ let hasher = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ hasher.init(Ci.nsICryptoHash.MD5);
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.data = str;
+ hasher.updateFromStream(stream, -1);
+ let binaryStr = hasher.finish(false);
+ let hexStr =
+ [("0" + binaryStr.charCodeAt(i).toString(16)).slice(-2) for (i in hash)].
+ join("");
+ return hexStr;
+}
diff --git a/browser/components/readinglist/test/xpcshell/test_SQLiteStore.js b/browser/components/readinglist/test/xpcshell/test_SQLiteStore.js
new file mode 100644
index 000000000000..998eba6428d6
--- /dev/null
+++ b/browser/components/readinglist/test/xpcshell/test_SQLiteStore.js
@@ -0,0 +1,314 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource:///modules/readinglist/SQLiteStore.jsm");
+Cu.import("resource://gre/modules/Sqlite.jsm");
+
+var gStore;
+var gItems;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* prepare() {
+ let basename = "reading-list-test.sqlite";
+ let dbFile = do_get_profile();
+ dbFile.append(basename);
+ function removeDB() {
+ if (dbFile.exists()) {
+ dbFile.remove(true);
+ }
+ }
+ removeDB();
+ do_register_cleanup(removeDB);
+
+ gStore = new SQLiteStore(dbFile.path);
+
+ gItems = [];
+ for (let i = 0; i < 3; i++) {
+ gItems.push({
+ guid: `guid${i}`,
+ url: `http://example.com/${i}`,
+ resolvedURL: `http://example.com/resolved/${i}`,
+ title: `title ${i}`,
+ excerpt: `excerpt ${i}`,
+ unread: true,
+ addedOn: i,
+ });
+ }
+
+ for (let item of gItems) {
+ yield gStore.addItem(item);
+ }
+});
+
+add_task(function* constraints() {
+ // add an item again
+ let err = null;
+ try {
+ yield gStore.addItem(gItems[0]);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err, "UNIQUE constraint failed");
+
+ // add a new item with an existing guid
+ function kindOfClone(item) {
+ let newItem = {};
+ for (let prop in item) {
+ newItem[prop] = item[prop];
+ if (typeof(newItem[prop]) == "string") {
+ newItem[prop] += " -- make this string different";
+ }
+ }
+ return newItem;
+ }
+ let item = kindOfClone(gItems[0]);
+ item.guid = gItems[0].guid;
+ err = null;
+ try {
+ yield gStore.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err, "UNIQUE constraint failed: items.guid");
+
+ // add a new item with an existing url
+ item = kindOfClone(gItems[0]);
+ item.url = gItems[0].url;
+ err = null;
+ try {
+ yield gStore.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err, "UNIQUE constraint failed: items.url");
+
+ // update an item with an existing url
+ item.guid = gItems[1].guid;
+ err = null;
+ try {
+ yield gStore.updateItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ // The failure actually happens on items.guid, not items.url, because the item
+ // is first looked up by url, and then its other properties are updated on the
+ // resulting row.
+ checkError(err, "UNIQUE constraint failed: items.guid");
+
+ // add a new item with an existing resolvedURL
+ item = kindOfClone(gItems[0]);
+ item.resolvedURL = gItems[0].resolvedURL;
+ err = null;
+ try {
+ yield gStore.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err, "UNIQUE constraint failed: items.resolvedURL");
+
+ // update an item with an existing resolvedURL
+ item.url = gItems[1].url;
+ err = null;
+ try {
+ yield gStore.updateItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err, "UNIQUE constraint failed: items.resolvedURL");
+
+ // add a new item with no guid, which is allowed
+ item = kindOfClone(gItems[0]);
+ delete item.guid;
+ err = null;
+ try {
+ yield gStore.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ Assert.ok(!err, err ? err.message : undefined);
+ let url1 = item.url;
+
+ // add a second new item with no guid, which is allowed
+ item = kindOfClone(gItems[1]);
+ delete item.guid;
+ err = null;
+ try {
+ yield gStore.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ Assert.ok(!err, err ? err.message : undefined);
+ let url2 = item.url;
+
+ // Delete both items since other tests assume the store contains only gItems.
+ yield gStore.deleteItemByURL(url1);
+ yield gStore.deleteItemByURL(url2);
+ let items = [];
+ yield gStore.forEachItem(i => items.push(i), { url: [url1, url2] });
+ Assert.equal(items.length, 0);
+
+ // add a new item with no url, which is not allowed
+ item = kindOfClone(gItems[0]);
+ delete item.url;
+ err = null;
+ try {
+ yield gStore.addItem(item);
+ }
+ catch (e) {
+ err = e;
+ }
+ checkError(err, "NOT NULL constraint failed: items.url");
+});
+
+add_task(function* count() {
+ let count = yield gStore.count();
+ Assert.equal(count, gItems.length);
+
+ count = yield gStore.count({
+ guid: gItems[0].guid,
+ });
+ Assert.equal(count, 1);
+});
+
+add_task(function* forEachItem() {
+ // all items
+ let items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ sort: "guid",
+ });
+ checkItems(items, gItems);
+
+ // first item
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ limit: 1,
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(0, 1));
+
+ // last item
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ limit: 1,
+ sort: "guid",
+ descending: true,
+ });
+ checkItems(items, gItems.slice(gItems.length - 1, gItems.length));
+
+ // match on a scalar property
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ guid: gItems[0].guid,
+ });
+ checkItems(items, gItems.slice(0, 1));
+
+ // match on an array
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ guid: gItems.map(i => i.guid),
+ sort: "guid",
+ });
+ checkItems(items, gItems);
+
+ // match on AND'ed properties
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ guid: gItems.map(i => i.guid),
+ title: gItems[0].title,
+ sort: "guid",
+ });
+ checkItems(items, [gItems[0]]);
+
+ // match on OR'ed properties
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ guid: gItems[1].guid,
+ sort: "guid",
+ }, {
+ guid: gItems[0].guid,
+ });
+ checkItems(items, [gItems[0], gItems[1]]);
+
+ // match on AND'ed and OR'ed properties
+ items = [];
+ yield gStore.forEachItem(item => items.push(item), {
+ guid: gItems.map(i => i.guid),
+ title: gItems[1].title,
+ sort: "guid",
+ }, {
+ guid: gItems[0].guid,
+ });
+ checkItems(items, [gItems[0], gItems[1]]);
+});
+
+add_task(function* updateItem() {
+ let newTitle = "a new title";
+ gItems[0].title = newTitle;
+ yield gStore.updateItem(gItems[0]);
+ let item;
+ yield gStore.forEachItem(i => item = i, {
+ guid: gItems[0].guid,
+ });
+ Assert.ok(item);
+ Assert.equal(item.title, gItems[0].title);
+});
+
+// This test deletes items so it should probably run last.
+add_task(function* deleteItemByURL() {
+ // delete first item
+ yield gStore.deleteItemByURL(gItems[0].url);
+ Assert.equal((yield gStore.count()), gItems.length - 1);
+ let items = [];
+ yield gStore.forEachItem(i => items.push(i), {
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(1));
+
+ // delete second item
+ yield gStore.deleteItemByURL(gItems[1].url);
+ Assert.equal((yield gStore.count()), gItems.length - 2);
+ items = [];
+ yield gStore.forEachItem(i => items.push(i), {
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(2));
+
+ // delete third item
+ yield gStore.deleteItemByURL(gItems[2].url);
+ Assert.equal((yield gStore.count()), gItems.length - 3);
+ items = [];
+ yield gStore.forEachItem(i => items.push(i), {
+ sort: "guid",
+ });
+ checkItems(items, gItems.slice(3));
+});
+
+function checkItems(actualItems, expectedItems) {
+ Assert.equal(actualItems.length, expectedItems.length);
+ for (let i = 0; i < expectedItems.length; i++) {
+ for (let prop in expectedItems[i]) {
+ Assert.ok(prop in actualItems[i], prop);
+ Assert.equal(actualItems[i][prop], expectedItems[i][prop]);
+ }
+ }
+}
+
+function checkError(err, expectedMsgSubstring) {
+ Assert.ok(err);
+ Assert.ok(err instanceof Cu.getGlobalForObject(Sqlite).Error);
+ Assert.ok(err.message);
+ Assert.ok(err.message.indexOf(expectedMsgSubstring) >= 0, err.message);
+}
diff --git a/browser/components/readinglist/test/xpcshell/xpcshell.ini b/browser/components/readinglist/test/xpcshell/xpcshell.ini
index e204dde472e9..c172cf0acf04 100644
--- a/browser/components/readinglist/test/xpcshell/xpcshell.ini
+++ b/browser/components/readinglist/test/xpcshell/xpcshell.ini
@@ -2,4 +2,6 @@
head = head.js
firefox-appdir = browser
+;[test_ReadingList.js]
[test_scheduler.js]
+;[test_SQLiteStore.js]
diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js
index 531d7df1a6a3..107cd8aa0e75 100644
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -206,7 +206,9 @@ let DebuggerController = {
if (target.isAddon) {
yield this._startAddonDebugging(actor);
- } else if (target.chrome) {
+ } else if (!target.isTabActor) {
+ // Some actors like AddonActor or RootActor for chrome debugging
+ // do not support attach/detach and can be used directly
yield this._startChromeDebugging(chromeDebugger);
} else {
yield this._startDebuggingTab();
diff --git a/browser/devtools/debugger/test/browser_dbg_chrome-debugging.js b/browser/devtools/debugger/test/browser_dbg_chrome-debugging.js
index 7043d3eebce6..256495d0d1f8 100644
--- a/browser/devtools/debugger/test/browser_dbg_chrome-debugging.js
+++ b/browser/devtools/debugger/test/browser_dbg_chrome-debugging.js
@@ -23,6 +23,7 @@ function test() {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
+ DebuggerServer.allowChromeProcess = true;
let transport = DebuggerServer.connectPipe();
gClient = new DebuggerClient(transport);
@@ -42,26 +43,26 @@ function test() {
}
function testChromeActor() {
- gClient.listTabs(aResponse => {
- ok(aResponse.chromeDebugger.contains("chromeDebugger"),
- "Chrome debugger actor should identify itself accordingly.");
-
+ gClient.attachProcess().then(aResponse => {
gClient.addListener("newGlobal", onNewGlobal);
gClient.addListener("newSource", onNewSource);
- gClient.attachThread(aResponse.chromeDebugger, (aResponse, aThreadClient) => {
- gThreadClient = aThreadClient;
+ let actor = aResponse.form.actor;
+ gClient.attachTab(actor, (response, tabClient) => {
+ tabClient.attachThread(null, (aResponse, aThreadClient) => {
+ gThreadClient = aThreadClient;
- if (aResponse.error) {
- ok(false, "Couldn't attach to the chrome debugger.");
- gAttached.reject();
- } else {
- ok(true, "Attached to the chrome debugger.");
- gAttached.resolve();
+ if (aResponse.error) {
+ ok(false, "Couldn't attach to the chrome debugger.");
+ gAttached.reject();
+ } else {
+ ok(true, "Attached to the chrome debugger.");
+ gAttached.resolve();
- // Ensure that a new chrome global will be created.
- gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
- }
+ // Ensure that a new chrome global will be created.
+ gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
+ }
+ });
});
});
}
diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js
index f510a56f74a9..99c21efd7cce 100644
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -563,6 +563,7 @@ AddonDebugger.prototype = {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
+ DebuggerServer.allowChromeProcess = true;
this.frame = document.createElement("iframe");
this.frame.setAttribute("height", 400);
@@ -581,7 +582,8 @@ AddonDebugger.prototype = {
let targetOptions = {
form: addonActor,
client: this.client,
- chrome: true
+ chrome: true,
+ isTabActor: false
};
let toolboxOptions = {
diff --git a/browser/devtools/devtools-clhandler.js b/browser/devtools/devtools-clhandler.js
index 45ab9d3638f1..8bb231fc2594 100644
--- a/browser/devtools/devtools-clhandler.js
+++ b/browser/devtools/devtools-clhandler.js
@@ -110,6 +110,7 @@ devtoolsCommandlineHandler.prototype = {
let debuggerServer = serverLoader.DebuggerServer;
debuggerServer.init();
debuggerServer.addBrowserActors();
+ debuggerServer.allowChromeProcess = true;
let listener = debuggerServer.createListener();
listener.portOrPath = portOrPath;
diff --git a/browser/devtools/framework/ToolboxProcess.jsm b/browser/devtools/framework/ToolboxProcess.jsm
index 482485f113b8..e446f13c7004 100644
--- a/browser/devtools/framework/ToolboxProcess.jsm
+++ b/browser/devtools/framework/ToolboxProcess.jsm
@@ -116,29 +116,31 @@ BrowserToolboxProcess.prototype = {
* Initializes the debugger server.
*/
_initServer: function() {
+ if (this.debuggerServer) {
+ dumpn("The chrome toolbox server is already running.");
+ return;
+ }
+
dumpn("Initializing the chrome toolbox server.");
- if (!this.loader) {
- // Create a separate loader instance, so that we can be sure to receive a
- // separate instance of the DebuggingServer from the rest of the devtools.
- // This allows us to safely use the tools against even the actors and
- // DebuggingServer itself, especially since we can mark this loader as
- // invisible to the debugger (unlike the usual loader settings).
- this.loader = new DevToolsLoader();
- this.loader.invisibleToDebugger = true;
- this.loader.main("devtools/server/main");
- this.debuggerServer = this.loader.DebuggerServer;
- dumpn("Created a separate loader instance for the DebuggerServer.");
+ // Create a separate loader instance, so that we can be sure to receive a
+ // separate instance of the DebuggingServer from the rest of the devtools.
+ // This allows us to safely use the tools against even the actors and
+ // DebuggingServer itself, especially since we can mark this loader as
+ // invisible to the debugger (unlike the usual loader settings).
+ this.loader = new DevToolsLoader();
+ this.loader.invisibleToDebugger = true;
+ this.loader.main("devtools/server/main");
+ this.debuggerServer = this.loader.DebuggerServer;
+ dumpn("Created a separate loader instance for the DebuggerServer.");
- // Forward interesting events.
- this.debuggerServer.on("connectionchange", this.emit.bind(this));
- }
+ // Forward interesting events.
+ this.debuggerServer.on("connectionchange", this.emit.bind(this));
- if (!this.debuggerServer.initialized) {
- this.debuggerServer.init();
- this.debuggerServer.addBrowserActors();
- dumpn("initialized and added the browser actors for the DebuggerServer.");
- }
+ this.debuggerServer.init();
+ this.debuggerServer.addBrowserActors();
+ this.debuggerServer.allowChromeProcess = true;
+ dumpn("initialized and added the browser actors for the DebuggerServer.");
let chromeDebuggingPort =
Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
diff --git a/browser/devtools/framework/connect/connect.js b/browser/devtools/framework/connect/connect.js
index c4cd31759d09..4a36da8724dc 100644
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -129,11 +129,19 @@ let onConnectionReady = Task.async(function*(aType, aTraits) {
let gParent = document.getElementById("globalActors");
// Build the Remote Process button
- if (Object.keys(globals).length > 1) {
+ // If Fx<37, tab actors were used to be exposed on RootActor
+ // but in Fx>=37, chrome is debuggable via attachProcess() and ChromeActor
+ if (globals.consoleActor || gClient.mainRoot.traits.allowChromeProcess) {
let a = document.createElement("a");
a.onclick = function() {
- openToolbox(globals, true);
-
+ if (gClient.mainRoot.traits.allowChromeProcess) {
+ gClient.attachProcess()
+ .then(aResponse => {
+ openToolbox(aResponse.form, true);
+ });
+ } else if (globals.consoleActor) {
+ openToolbox(globals, true, "webconsole", false);
+ }
}
a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
a.className = "remote-process";
@@ -162,7 +170,7 @@ let onConnectionReady = Task.async(function*(aType, aTraits) {
function buildAddonLink(addon, parent) {
let a = document.createElement("a");
a.onclick = function() {
- openToolbox(addon, true, "jsdebugger");
+ openToolbox(addon, true, "jsdebugger", false);
}
a.textContent = addon.name;
@@ -221,11 +229,12 @@ function handleConnectionTimeout() {
* The user clicked on one of the buttons.
* Opens the toolbox.
*/
-function openToolbox(form, chrome=false, tool="webconsole") {
+function openToolbox(form, chrome=false, tool="webconsole", isTabActor) {
let options = {
form: form,
client: gClient,
- chrome: chrome
+ chrome: chrome,
+ isTabActor: isTabActor
};
devtools.TargetFactory.forRemoteTab(options).then((target) => {
let hostType = devtools.Toolbox.HostType.WINDOW;
@@ -233,7 +242,7 @@ function openToolbox(form, chrome=false, tool="webconsole") {
toolbox.once("destroyed", function() {
gClient.close();
});
- });
+ }, console.error.bind(console));
window.close();
- });
+ }, console.error.bind(console));
}
diff --git a/browser/devtools/framework/gDevTools.jsm b/browser/devtools/framework/gDevTools.jsm
index e32ec0f4086c..23308315ef49 100644
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -697,6 +697,7 @@ let gDevToolsBrowser = {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
+ DebuggerServer.allowChromeProcess = true;
let transport = DebuggerServer.connectPipe();
let client = new DebuggerClient(transport);
@@ -718,7 +719,8 @@ let gDevToolsBrowser = {
let options = {
form: response.form,
client: client,
- chrome: true
+ chrome: true,
+ isTabActor: false
};
return devtools.TargetFactory.forRemoteTab(options);
})
diff --git a/browser/devtools/framework/target.js b/browser/devtools/framework/target.js
index 50c7531dd589..4b5ab9f97f6b 100644
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -175,6 +175,8 @@ function TabTarget(tab) {
this._client = tab.client;
this._chrome = tab.chrome;
}
+ // Default isTabActor to true if not explicitely specified
+ this._isTabActor = typeof(tab.isTabActor) == "boolean" ? tab.isTabActor : true;
}
TabTarget.prototype = {
@@ -315,10 +317,21 @@ TabTarget.prototype = {
return this._client;
},
+ // Tells us if we are debugging content document
+ // or if we are debugging chrome stuff.
+ // Allows to controls which features are available against
+ // a chrome or a content document.
get chrome() {
return this._chrome;
},
+ // Tells us if the related actor implements TabActor interface
+ // and requires to call `attach` request before being used
+ // and `detach` during cleanup
+ get isTabActor() {
+ return this._isTabActor;
+ },
+
get window() {
// XXX - this is a footgun for e10s - there .contentWindow will be null,
// and even though .contentWindowAsCPOW *might* work, it will not work
@@ -436,12 +449,13 @@ TabTarget.prototype = {
attachTab();
});
});
- } else if (!this.chrome) {
+ } else if (this.isTabActor) {
// In the remote debugging case, the protocol connection will have been
// already initialized in the connection screen code.
attachTab();
} else {
- // Remote chrome debugging doesn't need anything at this point.
+ // AddonActor and chrome debugging on RootActor doesn't inherits from TabActor and
+ // doesn't need to be attached.
this._remote.resolve(null);
}
diff --git a/browser/devtools/framework/test/browser_target_remote.js b/browser/devtools/framework/test/browser_target_remote.js
index d0e41746f1cb..d517dce356d2 100644
--- a/browser/devtools/framework/test/browser_target_remote.js
+++ b/browser/devtools/framework/test/browser_target_remote.js
@@ -1,10 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
-let { DebuggerServer } =
- Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
-let { DebuggerClient } =
- Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { devtools } =
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
@@ -12,28 +8,19 @@ let { devtools } =
function test() {
waitForExplicitFinish();
- if (!DebuggerServer.initialized) {
- DebuggerServer.init();
- DebuggerServer.addBrowserActors();
- }
+ getChromeActors((client, response) => {
+ let options = {
+ form: response,
+ client: client,
+ chrome: true
+ };
- var client = new DebuggerClient(DebuggerServer.connectPipe());
- client.connect(() => {
- client.listTabs(response => {
- let options = {
- form: response,
- client: client,
- chrome: true
- };
-
- devtools.TargetFactory.forRemoteTab(options).then(target => {
- target.on("close", () => {
- ok(true, "Target was closed");
- DebuggerServer.destroy();
- finish();
- });
- client.close();
+ devtools.TargetFactory.forRemoteTab(options).then(target => {
+ target.on("close", () => {
+ ok(true, "Target was closed");
+ finish();
});
+ client.close();
});
});
}
diff --git a/browser/devtools/framework/test/browser_target_support.js b/browser/devtools/framework/test/browser_target_support.js
index 8ab49b9a29b3..7e081fc20e12 100644
--- a/browser/devtools/framework/test/browser_target_support.js
+++ b/browser/devtools/framework/test/browser_target_support.js
@@ -4,10 +4,6 @@
// Test support methods on Target, such as `hasActor`, `getActorDescription`,
// `actorHasMethod` and `getTrait`.
-let { DebuggerServer } =
- Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
-let { DebuggerClient } =
- Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { devtools } =
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { Task } =
@@ -60,29 +56,20 @@ function* testTarget (client, target) {
function test() {
waitForExplicitFinish();
- if (!DebuggerServer.initialized) {
- DebuggerServer.init();
- DebuggerServer.addBrowserActors();
- }
+ getChromeActors((client, response) => {
+ let options = {
+ form: response,
+ client: client,
+ chrome: true
+ };
- var client = new DebuggerClient(DebuggerServer.connectPipe());
- client.connect(() => {
- client.listTabs(response => {
- let options = {
- form: response,
- client: client,
- chrome: true
- };
-
- devtools.TargetFactory.forRemoteTab(options).then(Task.async(testTarget).bind(null, client));
- });
+ devtools.TargetFactory.forRemoteTab(options).then(Task.async(testTarget).bind(null, client));
});
}
function close (target, client) {
target.on("close", () => {
ok(true, "Target was closed");
- DebuggerServer.destroy();
finish();
});
client.close();
diff --git a/browser/devtools/framework/test/head.js b/browser/devtools/framework/test/head.js
index f82e00ca493f..675d422f2c3c 100644
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -152,3 +152,26 @@ function toggleAllTools(state) {
}
}
}
+
+function getChromeActors(callback)
+{
+ let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+ let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect(() => {
+ client.attachProcess().then(response => {
+ callback(client, response.form);
+ });
+ });
+
+ SimpleTest.registerCleanupFunction(() => {
+ DebuggerServer.destroy();
+ });
+}
diff --git a/browser/devtools/framework/toolbox-process-window.js b/browser/devtools/framework/toolbox-process-window.js
index 7cc5c9dff04e..04daf855472a 100644
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -38,10 +38,12 @@ let connect = Task.async(function*() {
if (addonID) {
gClient.listAddons(({addons}) => {
let addonActor = addons.filter(addon => addon.id === addonID).pop();
- openToolbox(addonActor);
+ openToolbox({ form: addonActor, chrome: true, isTabActor: false });
});
} else {
- gClient.listTabs(openToolbox);
+ gClient.attachProcess().then(aResponse => {
+ openToolbox({ form: aResponse.form, chrome: true });
+ });
}
});
});
@@ -52,6 +54,7 @@ function setPrefDefaults() {
Services.prefs.setBoolPref("devtools.profiler.ui.show-platform-data", true);
Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", false);
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
+ Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
}
window.addEventListener("load", function() {
@@ -65,11 +68,12 @@ function onCloseCommand(event) {
window.close();
}
-function openToolbox(form) {
+function openToolbox({ form, chrome, isTabActor }) {
let options = {
form: form,
client: gClient,
- chrome: true
+ chrome: chrome,
+ isTabActor: isTabActor
};
devtools.TargetFactory.forRemoteTab(options).then(target => {
let frame = document.getElementById("toolbox-iframe");
diff --git a/browser/devtools/netmonitor/netmonitor-controller.js b/browser/devtools/netmonitor/netmonitor-controller.js
index 19dc7fed97c5..76192807078d 100644
--- a/browser/devtools/netmonitor/netmonitor-controller.js
+++ b/browser/devtools/netmonitor/netmonitor-controller.js
@@ -211,7 +211,9 @@ let NetMonitorController = {
let target = this._target;
let { client, form } = target;
- if (target.chrome) {
+ // Some actors like AddonActor or RootActor for chrome debugging
+ // do not support attach/detach and can be used directly
+ if (!target.isTabActor) {
this._startChromeMonitoring(client, form.consoleActor, deferred.resolve);
} else {
this._startMonitoringTab(client, form, deferred.resolve);
diff --git a/browser/devtools/performance/modules/front.js b/browser/devtools/performance/modules/front.js
index 076d0de1a219..c28d3743f422 100644
--- a/browser/devtools/performance/modules/front.js
+++ b/browser/devtools/performance/modules/front.js
@@ -121,14 +121,9 @@ PerformanceActorsConnection.prototype = {
* Initializes a connection to the profiler actor.
*/
_connectProfilerActor: Task.async(function*() {
- // Chrome debugging targets have already obtained a reference
- // to the profiler actor.
- if (this._target.chrome) {
- this._profiler = this._target.form.profilerActor;
- }
- // When we are debugging content processes, we already have the tab
- // specific one. Use it immediately.
- else if (this._target.form && this._target.form.profilerActor) {
+ // Chrome and content process targets already have obtained a reference
+ // to the profiler tab actor. Use it immediately.
+ if (this._target.form && this._target.form.profilerActor) {
this._profiler = this._target.form.profilerActor;
}
// Check if we already have a grip to the `listTabs` response object
diff --git a/browser/devtools/performance/test/browser_perf-overview-time-interval.js b/browser/devtools/performance/test/browser_perf-overview-time-interval.js
index cb14d6c9e13f..c6c23967af01 100644
--- a/browser/devtools/performance/test/browser_perf-overview-time-interval.js
+++ b/browser/devtools/performance/test/browser_perf-overview-time-interval.js
@@ -40,6 +40,8 @@ function spawnTest () {
yield notified;
let firstInterval = OverviewView.getTimeInterval();
+ info("First interval start time: " + firstInterval.startTime);
+ info("First interval end time: " + firstInterval.endTime);
ok(firstInterval.startTime - 10 < Number.EPSILON,
"The interval's start time was properly set.");
ok(firstInterval.endTime - 20 < Number.EPSILON,
@@ -56,6 +58,8 @@ function spawnTest () {
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, fail);
let secondInterval = OverviewView.getTimeInterval();
+ info("Second interval start time: " + secondInterval.startTime);
+ info("Second interval end time: " + secondInterval.endTime);
is(Math.round(secondInterval.startTime), 30,
"The interval's start time was properly set again.");
is(Math.round(secondInterval.endTime), 40,
diff --git a/browser/devtools/profiler/utils/shared.js b/browser/devtools/profiler/utils/shared.js
index 429efa2e507a..29bc36c9ccfc 100644
--- a/browser/devtools/profiler/utils/shared.js
+++ b/browser/devtools/profiler/utils/shared.js
@@ -83,14 +83,9 @@ ProfilerConnection.prototype = {
// Local debugging needs to make the target remote.
yield this._target.makeRemote();
- // Chrome debugging targets have already obtained a reference
- // to the profiler actor.
- if (this._target.chrome) {
- this._profiler = this._target.form.profilerActor;
- }
- // Or when we are debugging content processes, we already have the tab
- // specific one. Use it immediately.
- else if (this._target.form && this._target.form.profilerActor) {
+ // Chrome and content process targets already have obtained a reference
+ // to the profiler tab actor. Use it immediately.
+ if (this._target.form && this._target.form.profilerActor) {
this._profiler = this._target.form.profilerActor;
yield this._registerEventNotifications();
}
@@ -100,8 +95,9 @@ ProfilerConnection.prototype = {
this._profiler = this._target.root.profilerActor;
yield this._registerEventNotifications();
}
- // Otherwise, call `listTabs`.
- else {
+ // Otherwise, call `listTabs`, but ensure not trying to fetch tab actors
+ // for AddonTarget that are chrome, but do not expose profile at all.
+ else if (!this._target.chrome) {
this._profiler = (yield listTabs(this._client)).profilerActor;
yield this._registerEventNotifications();
}
diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js
index 1a381341ebf0..02e380b4c5aa 100644
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -2152,16 +2152,17 @@ ScratchpadWindow.prototype = Heritage.extend(ScratchpadTab.prototype, {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
+ DebuggerServer.allowChromeProcess = true;
let client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(() => {
- client.listTabs(aResponse => {
+ client.attachProcess().then(aResponse => {
if (aResponse.error) {
reportError("listTabs", aResponse);
deferred.reject(aResponse);
}
else {
- deferred.resolve({ form: aResponse, client: client });
+ deferred.resolve({ form: aResponse.form, client: client });
}
});
});
diff --git a/browser/devtools/shared/profiler/tree-model.js b/browser/devtools/shared/profiler/tree-model.js
index b513904cbb67..3a572726ba29 100644
--- a/browser/devtools/shared/profiler/tree-model.js
+++ b/browser/devtools/shared/profiler/tree-model.js
@@ -224,7 +224,7 @@ FrameNode.prototype = {
if (uri) {
functionName = this.location.substring(0, firstParenIndex - 1);
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
- hostName = uri.host;
+ hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
} else {
functionName = this.location;
url = null;
diff --git a/browser/devtools/webconsole/hudservice.js b/browser/devtools/webconsole/hudservice.js
index e8ef0fe650f6..4ec04907c85e 100644
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -187,22 +187,16 @@ HUD_SERVICE.prototype =
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
+ DebuggerServer.allowChromeProcess = true;
let client = new DebuggerClient(DebuggerServer.connectPipe());
- client.connect(() =>
- client.listTabs((aResponse) => {
- // Add Global Process debugging...
- let globals = Cu.cloneInto(aResponse, {});
- delete globals.tabs;
- delete globals.selected;
- // ...only if there are appropriate actors (a 'from' property will
- // always be there).
- if (Object.keys(globals).length > 1) {
- deferred.resolve({ form: globals, client: client, chrome: true });
- } else {
- deferred.reject("Global console not found!");
- }
- }));
+ client.connect(() => {
+ client.attachProcess().then(aResponse => {
+ // Set chrome:false in order to attach to the target
+ // (i.e. send an `attach` request to the chrome actor)
+ deferred.resolve({ form: aResponse.form, client: client, chrome: false });
+ }, deferred.reject);
+ });
return deferred.promise;
}
@@ -210,13 +204,7 @@ HUD_SERVICE.prototype =
let target;
function getTarget(aConnection)
{
- let options = {
- form: aConnection.form,
- client: aConnection.client,
- chrome: true,
- };
-
- return devtools.TargetFactory.forRemoteTab(options);
+ return devtools.TargetFactory.forRemoteTab(aConnection);
}
function openWindow(aTarget)
@@ -241,12 +229,12 @@ HUD_SERVICE.prototype =
}
connect().then(getTarget).then(openWindow).then((aWindow) => {
- this.openBrowserConsole(target, aWindow, aWindow)
+ return this.openBrowserConsole(target, aWindow, aWindow)
.then((aBrowserConsole) => {
this._browserConsoleDefer.resolve(aBrowserConsole);
this._browserConsoleDefer = null;
})
- }, console.error);
+ }, console.error.bind(console));
return this._browserConsoleDefer.promise;
},
@@ -640,10 +628,13 @@ WebConsole.prototype = {
this._destroyer = promise.defer();
- let popupset = this.mainPopupSet;
- let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
- for (let panel of panels) {
- panel.hidePopup();
+ // The document may already be removed
+ if (this.chromeUtilsWindow && this.mainPopupSet) {
+ let popupset = this.mainPopupSet;
+ let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
+ for (let panel of panels) {
+ panel.hidePopup();
+ }
}
let onDestroy = function WC_onDestroyUI() {
diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js
index 6f52424298d1..cfdbdec38cd9 100644
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -440,7 +440,11 @@ WebConsoleFrame.prototype = {
* @type boolean
*/
get persistLog() {
- return Services.prefs.getBoolPref(PREF_PERSISTLOG);
+ // For the browser console, we receive tab navigation
+ // when the original top level window we attached to is closed,
+ // but we don't want to reset console history and just switch to
+ // the next available window.
+ return this.owner._browserConsole || Services.prefs.getBoolPref(PREF_PERSISTLOG);
},
/**
@@ -3426,7 +3430,7 @@ JSTerm.prototype = {
let selectedNodeActor = null;
let inspectorSelection = this.hud.owner.getInspectorSelection();
- if (inspectorSelection) {
+ if (inspectorSelection && inspectorSelection.nodeFront) {
selectedNodeActor = inspectorSelection.nodeFront.actorID;
}
@@ -5095,7 +5099,7 @@ WebConsoleConnectionProxy.prototype = {
this.target.on("navigate", this._onTabNavigated);
this._consoleActor = this.target.form.consoleActor;
- if (!this.target.chrome) {
+ if (this.target.isTabActor) {
let tab = this.target.form;
this.owner.onLocationChange(tab.url, tab.title);
}
diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js
index 68103d31ffe7..67c147eec25f 100644
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -927,8 +927,10 @@ let UI = {
// We can't know for sure which one was used here, so reset the
// |toolboxPromise| since someone must be destroying it to reach here,
// and call our own close method.
- this.toolboxPromise = null;
- this._closeToolboxUI();
+ if (this.toolboxIframe && this.toolboxIframe.uid == json.uid) {
+ this.toolboxPromise = null;
+ this._closeToolboxUI();
+ }
break;
}
} catch(e) { console.error(e); }
@@ -941,9 +943,9 @@ let UI = {
this.toolboxPromise = null;
return toolboxPromise.then(toolbox => {
return toolbox.destroy();
- }).catch(console.error)
+ }).then(null, console.error)
.then(() => this._closeToolboxUI())
- .catch(console.error);
+ .then(null, console.error);
}
return promise.resolve();
},
@@ -966,9 +968,13 @@ let UI = {
let iframe = document.createElement("iframe");
iframe.id = "toolbox";
+ // Compute a uid on the iframe in order to identify toolbox iframe
+ // when receiving toolbox-close event
+ iframe.uid = new Date().getTime();
+
document.querySelector("notificationbox").insertBefore(iframe, splitter.nextSibling);
let host = devtools.Toolbox.HostType.CUSTOM;
- let options = { customIframe: iframe, zoom: false };
+ let options = { customIframe: iframe, zoom: false, uid: iframe.uid };
this.toolboxIframe = iframe;
let height = Services.prefs.getIntPref("devtools.toolbox.footer.height");
diff --git a/browser/devtools/webide/modules/app-manager.js b/browser/devtools/webide/modules/app-manager.js
index 42233a2d50fc..4fe0f2bb9deb 100644
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -217,11 +217,25 @@ let AppManager = exports.AppManager = {
getTarget: function() {
if (this.selectedProject.type == "mainProcess") {
- return devtools.TargetFactory.forRemoteTab({
- form: this._listTabsResponse,
- client: this.connection.client,
- chrome: true
- });
+ // Fx >=37 exposes a ChromeActor to debug the main process
+ if (this.connection.client.mainRoot.traits.allowChromeProcess) {
+ return this.connection.client.attachProcess()
+ .then(aResponse => {
+ return devtools.TargetFactory.forRemoteTab({
+ form: aResponse.form,
+ client: this.connection.client,
+ chrome: true
+ });
+ });
+ } else {
+ // Fx <37 exposes tab actors on the root actor
+ return devtools.TargetFactory.forRemoteTab({
+ form: this._listTabsResponse,
+ client: this.connection.client,
+ chrome: true,
+ isTabActor: false
+ });
+ }
}
if (this.selectedProject.type == "tab") {
@@ -414,8 +428,12 @@ let AppManager = exports.AppManager = {
},
isMainProcessDebuggable: function() {
- return this._listTabsResponse &&
- this._listTabsResponse.consoleActor;
+ // Fx <37 exposes chrome tab actors on RootActor
+ // Fx >=37 exposes a dedicated actor via attachProcess request
+ return this.connection.client &&
+ this.connection.client.mainRoot.traits.allowChromeProcess ||
+ (this._listTabsResponse &&
+ this._listTabsResponse.consoleActor);
},
get deviceFront() {
diff --git a/browser/devtools/webide/modules/runtimes.js b/browser/devtools/webide/modules/runtimes.js
index f6c3cf7ef205..1a8ff4052ebb 100644
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -575,6 +575,7 @@ let gLocalRuntime = {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
+ DebuggerServer.allowChromeProcess = true;
connection.host = null; // Force Pipe transport
connection.port = null;
connection.connect();
diff --git a/browser/modules/Social.jsm b/browser/modules/Social.jsm
index cf48542eb561..3982b525f54e 100644
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -24,6 +24,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
"resource://gre/modules/SocialService.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
+ "resource://gre/modules/PageMetadata.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
@@ -31,9 +33,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "unescapeService",
- "@mozilla.org/feed-unescapehtml;1",
- "nsIScriptableUnescapeHTML");
function promiseSetAnnotation(aURI, providerList) {
let deferred = Promise.defer();
@@ -528,180 +527,4 @@ this.OpenGraphBuilder = {
endpointURL = endpointURL + "?" + str.join("&");
return endpointURL;
},
-
- getData: function(aDocument, target) {
- let res = {
- url: this._validateURL(aDocument, aDocument.documentURI),
- title: aDocument.title,
- previews: []
- };
- this._getMetaData(aDocument, res);
- this._getLinkData(aDocument, res);
- this._getPageData(aDocument, res);
- res.microdata = this.getMicrodata(aDocument, target);
- return res;
- },
-
- getMicrodata: function (aDocument, target) {
- return getMicrodata(aDocument, target);
- },
-
- _getMetaData: function(aDocument, o) {
- // query for standardized meta data
- let els = aDocument.querySelectorAll("head > meta[property], head > meta[name]");
- if (els.length < 1)
- return;
- let url;
- for (let el of els) {
- let value = el.getAttribute("content")
- if (!value)
- continue;
- value = unescapeService.unescape(value.trim());
- let key = el.getAttribute("property") || el.getAttribute("name");
- if (!key)
- continue;
- // There are a wide array of possible meta tags, expressing articles,
- // products, etc. so all meta tags are passed through but we touch up the
- // most common attributes.
- o[key] = value;
- switch (key) {
- case "title":
- case "og:title":
- o.title = value;
- break;
- case "description":
- case "og:description":
- o.description = value;
- break;
- case "og:site_name":
- o.siteName = value;
- break;
- case "medium":
- case "og:type":
- o.medium = value;
- break;
- case "og:video":
- url = this._validateURL(aDocument, value);
- if (url)
- o.source = url;
- break;
- case "og:url":
- url = this._validateURL(aDocument, value);
- if (url)
- o.url = url;
- break;
- case "og:image":
- url = this._validateURL(aDocument, value);
- if (url)
- o.previews.push(url);
- break;
- }
- }
- },
-
- _getLinkData: function(aDocument, o) {
- let els = aDocument.querySelectorAll("head > link[rel], head > link[id]");
- for (let el of els) {
- let url = el.getAttribute("href");
- if (!url)
- continue;
- url = this._validateURL(aDocument, unescapeService.unescape(url.trim()));
- switch (el.getAttribute("rel") || el.getAttribute("id")) {
- case "shorturl":
- case "shortlink":
- o.shortUrl = url;
- break;
- case "canonicalurl":
- case "canonical":
- o.url = url;
- break;
- case "image_src":
- o.previews.push(url);
- break;
- case "alternate":
- // expressly for oembed support but we're liberal here and will let
- // other alternate links through. oembed defines an href, supplied by
- // the site, where you can fetch additional meta data about a page.
- // We'll let the client fetch the oembed data themselves, but they
- // need the data from this link.
- if (!o.alternate)
- o.alternate = [];
- o.alternate.push({
- "type": el.getAttribute("type"),
- "href": el.getAttribute("href"),
- "title": el.getAttribute("title")
- })
- }
- }
- },
-
- // scrape through the page for data we want
- _getPageData: function(aDocument, o) {
- if (o.previews.length < 1)
- o.previews = this._getImageUrls(aDocument);
- },
-
- _validateURL: function(aDocument, url) {
- let docURI = Services.io.newURI(aDocument.documentURI, null, null);
- let uri = Services.io.newURI(docURI.resolve(url), null, null);
- if (["http", "https", "ftp", "ftps"].indexOf(uri.scheme) < 0)
- return null;
- uri.userPass = "";
- return uri.spec;
- },
-
- _getImageUrls: function(aDocument) {
- let l = [];
- let els = aDocument.querySelectorAll("img");
- for (let el of els) {
- let src = el.getAttribute("src");
- if (src) {
- l.push(this._validateURL(aDocument, unescapeService.unescape(src)));
- // we don't want a billion images
- if (l.length > 5)
- break;
- }
- }
- return l;
- }
};
-
-// getMicrodata (and getObject) based on wg algorythm to convert microdata to json
-// http://www.whatwg.org/specs/web-apps/current-work/multipage/microdata-2.html#json
-function getMicrodata(document, target) {
-
- function _getObject(item) {
- let result = {};
- if (item.itemType.length)
- result.types = [i for (i of item.itemType)];
- if (item.itemId)
- result.itemId = item.itemid;
- if (item.properties.length)
- result.properties = {};
- for (let elem of item.properties) {
- let value;
- if (elem.itemScope)
- value = _getObject(elem);
- else if (elem.itemValue)
- value = elem.itemValue;
- // handle mis-formatted microdata
- else if (elem.hasAttribute("content"))
- value = elem.getAttribute("content");
-
- for (let prop of elem.itemProp) {
- if (!result.properties[prop])
- result.properties[prop] = [];
- result.properties[prop].push(value);
- }
- }
- return result;
- }
-
- let result = { items: [] };
- let elms = target ? [target] : document.getItems();
- for (let el of elms) {
- if (el.itemScope)
- result.items.push(_getObject(el));
- }
- return result;
-}
diff --git a/browser/themes/windows/customizableui/panelUIOverlay.css b/browser/themes/windows/customizableui/panelUIOverlay.css
index 7785ddb2ae63..18a9cb0d8f8d 100644
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -237,6 +237,8 @@ menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
menuitem.panel-subview-footer@menuStateHover@,
.subviewbutton.panel-subview-footer@buttonStateHover@,
.subviewbutton.panel-subview-footer@buttonStateActive@,
+ .subviewbutton@menuStateHover@ > .menu-accel-container,
+ .PanelUI-subView .subviewbutton[shortcut]@buttonStateHover@::after,
#BMB_bookmarksPopup .panel-subview-footer@menuStateHover@ > .menu-text {
background-color: Highlight;
color: highlighttext !important;
diff --git a/config/external/nss/nss.def b/config/external/nss/nss.def
index 732f2b07a771..c333bcd5d5a1 100644
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -525,7 +525,6 @@ SECKEY_ExtractPublicKey
SECKEY_GetPublicKeyType
SECKEY_ImportDERPublicKey
SECKEY_PublicKeyStrength
-SECKEY_PublicKeyStrengthInBits
SECKEY_RSAPSSParamsTemplate DATA
SECKEY_SignatureLen
SECMIME_DecryptionAllowed
diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp
index a43b53b50a42..efccae61af50 100644
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -858,7 +858,7 @@ Console::Assert(JSContext* aCx, bool aCondition,
METHOD(Count, "count")
void
-Console::__noSuchMethod__()
+Console::NoopMethod()
{
// Nothing to do.
}
diff --git a/dom/base/Console.h b/dom/base/Console.h
index e33fa799f34b..bc096793f834 100644
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -101,7 +101,7 @@ public:
Count(JSContext* aCx, const Sequence& aData);
void
- __noSuchMethod__();
+ NoopMethod();
private:
enum MethodName
diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp
index d84d198dfde7..8508ac9e18d1 100644
--- a/dom/base/DOMException.cpp
+++ b/dom/base/DOMException.cpp
@@ -723,31 +723,5 @@ DOMException::Create(nsresult aRv)
return inst.forget();
}
-bool
-DOMException::Sanitize(JSContext* aCx,
- JS::MutableHandle aSanitizedValue)
-{
- nsRefPtr retval = this;
- if (mLocation && !mLocation->CallerSubsumes(aCx)) {
- nsString message;
- GetMessageMoz(message);
- nsString name;
- GetName(name);
- retval = new dom::DOMException(nsresult(Result()),
- NS_ConvertUTF16toUTF8(message),
- NS_ConvertUTF16toUTF8(name),
- Code());
- // Now it's possible that the stack on retval still starts with
- // stuff aCx is not supposed to touch; it depends on what's on the
- // stack right this second. Walk past all of that.
- nsCOMPtr stack;
- nsresult rv = retval->mLocation->GetSanitized(aCx, getter_AddRefs(stack));
- NS_ENSURE_SUCCESS(rv, false);
- retval->mLocation.swap(stack);
- }
-
- return ToJSValue(aCx, retval, aSanitizedValue);
-}
-
} // namespace dom
} // namespace mozilla
diff --git a/dom/base/DOMException.h b/dom/base/DOMException.h
index 5e0fa6e6db6c..89bfd1e8669d 100644
--- a/dom/base/DOMException.h
+++ b/dom/base/DOMException.h
@@ -156,15 +156,6 @@ public:
static already_AddRefed
Create(nsresult aRv);
- // Sanitize() is a workaround for the fact that DOMExceptions can leak stack
- // information for the first stackframe to callers that should not have access
- // to it. To prevent this, we check whether aCx subsumes our first stackframe
- // and if not hand out a JS::Value for a clone of ourselves. Otherwise we
- // hand out a JS::Value for ourselves.
- //
- // If the return value is false, an exception was thrown on aCx.
- bool Sanitize(JSContext* aCx, JS::MutableHandle aSanitizedValue);
-
protected:
virtual ~DOMException() {}
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
index ecd47243ea3a..466911d20bbd 100644
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -797,6 +797,15 @@ Element::ScrollByNoFlush(int32_t aDx, int32_t aDy)
return (before != after);
}
+void
+Element::MozScrollSnap()
+{
+ nsIScrollableFrame* sf = GetScrollFrame(nullptr, false);
+ if (sf) {
+ sf->ScrollSnap();
+ }
+}
+
static nsSize GetScrollRectSizeForOverflowVisibleFrame(nsIFrame* aFrame)
{
if (!aFrame) {
diff --git a/dom/base/Element.h b/dom/base/Element.h
index 929f566cadca..ca627fdaa85c 100644
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -762,6 +762,7 @@ public:
void SetScrollLeft(int32_t aScrollLeft);
int32_t ScrollWidth();
int32_t ScrollHeight();
+ void MozScrollSnap();
int32_t ClientTop()
{
return nsPresContext::AppUnitsToIntCSSPixels(GetClientAreaRect().y);
diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp
index cdb2c072ab6b..c7606ad2dc75 100644
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -257,11 +257,15 @@ static nsDOMClassInfoData sClassInfoData[] = {
NS_DEFINE_CLASSINFO_DATA(CSSFontFaceRule, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
- NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentFrameMessageManager, nsEventTargetSH,
+ NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentFrameMessageManager,
+ nsMessageManagerSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS |
+ nsIXPCScriptable::WANT_ENUMERATE |
nsIXPCScriptable::IS_GLOBAL_OBJECT)
- NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentProcessMessageManager, nsDOMGenericSH,
+ NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ContentProcessMessageManager,
+ nsMessageManagerSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS |
+ nsIXPCScriptable::WANT_ENUMERATE |
nsIXPCScriptable::IS_GLOBAL_OBJECT)
NS_DEFINE_CHROME_ONLY_CLASSINFO_DATA(ChromeMessageBroadcaster, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -2587,3 +2591,42 @@ nsNonDOMObjectSH::GetFlags(uint32_t *aFlags)
*aFlags = nsIClassInfo::MAIN_THREAD_ONLY | nsIClassInfo::SINGLETON_CLASSINFO;
return NS_OK;
}
+
+// nsContentFrameMessageManagerSH
+
+template
+NS_IMETHODIMP
+nsMessageManagerSH::Resolve(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx, JSObject* obj_,
+ jsid id_, bool* resolvedp,
+ bool* _retval)
+{
+ JS::Rooted obj(cx, obj_);
+ JS::Rooted id(cx, id_);
+
+ *_retval = SystemGlobalResolve(cx, obj, id, resolvedp);
+ NS_ENSURE_TRUE(*_retval, NS_ERROR_FAILURE);
+
+ if (*resolvedp) {
+ return NS_OK;
+ }
+
+ return Super::Resolve(wrapper, cx, obj, id, resolvedp, _retval);
+}
+
+template
+NS_IMETHODIMP
+nsMessageManagerSH::Enumerate(nsIXPConnectWrappedNative* wrapper,
+ JSContext* cx, JSObject* obj_,
+ bool* _retval)
+{
+ JS::Rooted obj(cx, obj_);
+
+ *_retval = SystemGlobalEnumerate(cx, obj);
+ NS_ENSURE_TRUE(*_retval, NS_ERROR_FAILURE);
+
+ // Don't call up to our superclass, since neither nsDOMGenericSH nor
+ // nsEventTargetSH have WANT_ENUMERATE.
+ MOZ_ASSERT(!(this->GetScriptableFlags() & nsIXPCScriptable::WANT_ENUMERATE));
+ return NS_OK;
+}
diff --git a/dom/base/nsDOMClassInfo.h b/dom/base/nsDOMClassInfo.h
index 6badc33ca0d7..11fd7ce001e2 100644
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -313,4 +313,29 @@ public:
}
};
+template
+class nsMessageManagerSH : public Super
+{
+protected:
+ explicit nsMessageManagerSH(nsDOMClassInfoData* aData)
+ : Super(aData)
+ {
+ }
+
+ virtual ~nsMessageManagerSH()
+ {
+ }
+public:
+ NS_IMETHOD Resolve(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
+ JSObject* obj_, jsid id_, bool* resolvedp,
+ bool* _retval) MOZ_OVERRIDE;
+ NS_IMETHOD Enumerate(nsIXPConnectWrappedNative* wrapper, JSContext* cx,
+ JSObject* obj_, bool* _retval) MOZ_OVERRIDE;
+
+ static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
+ {
+ return new nsMessageManagerSH(aData);
+ }
+};
+
#endif /* nsDOMClassInfo_h___ */
diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp
index fb1e05bf4428..2fc711194227 100644
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -185,7 +185,7 @@ nsFrameLoader::~nsFrameLoader()
if (mMessageManager) {
mMessageManager->Disconnect();
}
- nsFrameLoader::Destroy();
+ MOZ_RELEASE_ASSERT(mDestroyCalled);
}
nsFrameLoader*
@@ -2665,17 +2665,40 @@ nsFrameLoader::RequestNotifyAfterRemotePaint()
// If remote browsing (e10s), handle this with the TabParent.
if (mRemoteBrowser) {
unused << mRemoteBrowser->SendRequestNotifyAfterRemotePaint();
- return NS_OK;
}
- // If not remote browsing, directly use the document's window.
- nsCOMPtr window = do_GetInterface(mDocShell);
- if (!window) {
- NS_WARNING("Unable to get window for synchronous MozAfterRemotePaint event.");
- return NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::RequestNotifyLayerTreeReady()
+{
+ if (mRemoteBrowser) {
+ return mRemoteBrowser->RequestNotifyLayerTreeReady() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
}
- window->SetRequestNotifyAfterRemotePaint();
+ nsRefPtr event =
+ new AsyncEventDispatcher(mOwnerContent,
+ NS_LITERAL_STRING("MozLayerTreeReady"),
+ true, false);
+ event->PostDOMEvent();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFrameLoader::RequestNotifyLayerTreeCleared()
+{
+ if (mRemoteBrowser) {
+ return mRemoteBrowser->RequestNotifyLayerTreeCleared() ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsRefPtr event =
+ new AsyncEventDispatcher(mOwnerContent,
+ NS_LITERAL_STRING("MozLayerTreeCleared"),
+ true, false);
+ event->PostDOMEvent();
+
return NS_OK;
}
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
index 2dab9f09b6fa..cddbdac7224a 100644
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -585,7 +585,7 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindow *aOuterWindow)
mInnerWindow(nullptr), mOuterWindow(aOuterWindow),
// Make sure no actual window ends up with mWindowID == 0
mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
- mMarkedCCGeneration(0), mSendAfterRemotePaint(false)
+ mMarkedCCGeneration(0)
{}
nsPIDOMWindow::~nsPIDOMWindow() {}
@@ -3773,21 +3773,6 @@ nsPIDOMWindow::RefreshMediaElements()
service->RefreshAgentsVolume(this);
}
-void
-nsPIDOMWindow::SendAfterRemotePaintIfRequested()
-{
- if (!mSendAfterRemotePaint) {
- return;
- }
-
- mSendAfterRemotePaint = false;
-
- nsContentUtils::DispatchChromeEvent(GetExtantDoc(),
- GetParentTarget(),
- NS_LITERAL_STRING("MozAfterRemotePaint"),
- false, false);
-}
-
// nsISpeechSynthesisGetter
#ifdef MOZ_WEBSPEECH
@@ -7355,6 +7340,16 @@ nsGlobalWindow::ScrollByPages(int32_t numPages,
}
}
+void
+nsGlobalWindow::MozScrollSnap()
+{
+ FlushPendingNotifications(Flush_Layout);
+ nsIScrollableFrame *sf = GetScrollFrame();
+ if (sf) {
+ sf->ScrollSnap();
+ }
+}
+
void
nsGlobalWindow::MozRequestOverfill(OverfillCallback& aCallback,
mozilla::ErrorResult& aError)
diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h
index 8e48f059ec71..76dbfb3fa7f3 100644
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -670,6 +670,26 @@ public:
mCleanedUp);
}
+ bool
+ HadOriginalOpener() const
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ return mHadOriginalOpener;
+ }
+
+ bool
+ IsTopLevelWindow()
+ {
+ MOZ_ASSERT(IsOuterWindow());
+ nsCOMPtr parentWindow;
+ nsresult rv = GetScriptableTop(getter_AddRefs(parentWindow));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return parentWindow == static_cast(this);
+ }
+
virtual void
FirePopupBlockedEvent(nsIDocument* aDoc,
nsIURI* aPopupURI,
@@ -946,6 +966,7 @@ public:
const mozilla::dom::ScrollOptions& aOptions);
void ScrollByPages(int32_t numPages,
const mozilla::dom::ScrollOptions& aOptions);
+ void MozScrollSnap();
int32_t GetInnerWidth(mozilla::ErrorResult& aError);
void SetInnerWidth(int32_t aInnerWidth, mozilla::ErrorResult& aError);
int32_t GetInnerHeight(mozilla::ErrorResult& aError);
diff --git a/dom/base/nsIFrameLoader.idl b/dom/base/nsIFrameLoader.idl
index b60a8ff20f40..0d5713090233 100644
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -16,7 +16,7 @@ interface nsIDOMElement;
interface nsITabParent;
interface nsILoadContext;
-[scriptable, builtinclass, uuid(28b6b043-46ec-412f-9be9-db22938b0d6d)]
+[scriptable, builtinclass, uuid(d24f9330-ae4e-11e4-ab27-0800200c9a66)]
interface nsIFrameLoader : nsISupports
{
/**
@@ -121,6 +121,14 @@ interface nsIFrameLoader : nsISupports
*/
void requestNotifyAfterRemotePaint();
+ /**
+ * Request an event when the layer tree from the remote tab becomes
+ * available or unavailable. When this happens, a mozLayerTreeReady
+ * or mozLayerTreeCleared event is fired.
+ */
+ void requestNotifyLayerTreeReady();
+ void requestNotifyLayerTreeCleared();
+
/**
* The default event mode automatically forwards the events
* handled in EventStateManager::HandleCrossProcessEvent to
diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h
index 731053be0879..755dfdd4a7a1 100644
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -751,17 +751,6 @@ public:
return mMarkedCCGeneration;
}
- // Sets the condition that we send an NS_AFTER_REMOTE_PAINT message just before the next
- // composite. Used in non-e10s implementations.
- void SetRequestNotifyAfterRemotePaint()
- {
- mSendAfterRemotePaint = true;
- }
-
- // Sends an NS_AFTER_REMOTE_PAINT message if requested by
- // SetRequestNotifyAfterRemotePaint().
- void SendAfterRemotePaintIfRequested();
-
protected:
// The nsPIDOMWindow constructor. The aOuterWindow argument should
// be null if and only if the created window itself is an outer
@@ -854,10 +843,6 @@ protected:
bool mHasNotifiedGlobalCreated;
uint32_t mMarkedCCGeneration;
-
- // If true, send an NS_AFTER_REMOTE_PAINT message before compositing in a
- // non-e10s implementation.
- bool mSendAfterRemotePaint;
};
diff --git a/dom/base/test/chrome/chrome.ini b/dom/base/test/chrome/chrome.ini
index d13f66c318be..d64303053160 100644
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -14,6 +14,7 @@ support-files =
file_bug990812-3.xul
file_bug990812-4.xul
file_bug990812-5.xul
+ file_bug1139964.xul
fileconstructor_file.png
frame_bug814638.xul
host_bug814638.xul
@@ -55,6 +56,7 @@ skip-if = buildapp == 'mulet'
[test_bug914381.html]
[test_bug990812.xul]
[test_bug1063837.xul]
+[test_bug1139964.xul]
[test_cpows.xul]
skip-if = buildapp == 'mulet'
[test_document_register.xul]
diff --git a/dom/base/test/chrome/file_bug1139964.xul b/dom/base/test/chrome/file_bug1139964.xul
new file mode 100644
index 000000000000..251334301401
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1139964.xul
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/base/test/chrome/test_bug1139964.xul b/dom/base/test/chrome/test_bug1139964.xul
new file mode 100644
index 000000000000..f0a7cefd7fc1
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1139964.xul
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+ Mozilla Bug 1139964
+
+
+
+
+
diff --git a/dom/base/test/csp/file_connect-src-fetch.html b/dom/base/test/csp/file_connect-src-fetch.html
new file mode 100644
index 000000000000..832ff8b5b102
--- /dev/null
+++ b/dom/base/test/csp/file_connect-src-fetch.html
@@ -0,0 +1,16 @@
+
+
+
+ Bug 1139667 - Test mapping of fetch() to connect-src
+
+
+
+
+
diff --git a/dom/base/test/csp/mochitest.ini b/dom/base/test/csp/mochitest.ini
index bec4bc299d4e..a204c49a1e38 100644
--- a/dom/base/test/csp/mochitest.ini
+++ b/dom/base/test/csp/mochitest.ini
@@ -2,6 +2,7 @@
support-files =
file_base-uri.html
file_connect-src.html
+ file_connect-src-fetch.html
file_CSP.css
file_CSP.sjs
file_csp_allow_https_schemes.html
diff --git a/dom/base/test/csp/test_connect-src.html b/dom/base/test/csp/test_connect-src.html
index 837065e976b3..c61fb39ff0be 100644
--- a/dom/base/test/csp/test_connect-src.html
+++ b/dom/base/test/csp/test_connect-src.html
@@ -1,7 +1,7 @@
- Bug 1031530 - Test mapping of XMLHttpRequest to connect-src
+ Bug 1031530 and Bug 1139667 - Test mapping of XMLHttpRequest and fetch() to connect-src
@@ -16,7 +16,7 @@
/*
* Description of the test:
- * We load a page with a given CSP and verify that XMLHttpRequests are correctly
+ * We load a page with a given CSP and verify that XMLHttpRequests and fetches are correctly
* evaluated through the "connect-src" directive. All XMLHttpRequests are served
* using http://mochi.test:8888, which allows the requests to succeed for the first
* two policies and to fail for the last policy. Please note that we have to add
@@ -27,14 +27,32 @@ SimpleTest.waitForExplicitFinish();
var tests = [
{
+ file: "file_connect-src.html",
result : "allowed",
policy : "default-src 'none' script-src 'unsafe-inline'; connect-src http://mochi.test:8888"
},
{
+ file: "file_connect-src.html",
result : "allowed",
policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src *"
},
{
+ file: "file_connect-src.html",
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src http://www.example.com"
+ },
+ {
+ file: "file_connect-src-fetch.html",
+ result : "allowed",
+ policy : "default-src 'none' script-src 'unsafe-inline'; connect-src http://mochi.test:8888"
+ },
+ {
+ file: "file_connect-src-fetch.html",
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src *"
+ },
+ {
+ file: "file_connect-src-fetch.html",
result : "blocked",
policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src http://www.example.com"
}
@@ -96,7 +114,7 @@ function loadNextTest() {
var src = "file_csp_testserver.sjs";
// append the file that should be served
- src += "?file=" + escape("tests/dom/base/test/csp/file_connect-src.html");
+ src += "?file=" + escape("tests/dom/base/test/csp/" + tests[counter].file);
// append the CSP that should be used to serve the file
src += "&csp=" + escape(tests[counter].policy);
@@ -104,7 +122,9 @@ function loadNextTest() {
}
// start running the tests
-loadNextTest();
+SpecialPowers.pushPrefEnv({"set": [
+ ["dom.fetch.enabled", true]
+]}, loadNextTest);
diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp
index 30c50b5ab274..7d388b71462c 100644
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -42,6 +42,7 @@
#include "mozilla/dom/HTMLEmbedElementBinding.h"
#include "mozilla/dom/HTMLAppletElementBinding.h"
#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ResolveSystemBinding.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
#include "WorkerPrivate.h"
#include "nsDOMClassInfo.h"
@@ -249,26 +250,6 @@ ErrorResult::ReportJSExceptionFromJSImplementation(JSContext* aCx)
nsresult rv =
UNWRAP_OBJECT(DOMException, &mJSException.toObject(), domException);
if (NS_SUCCEEDED(rv)) {
- // We may have to create a new DOMException object, because the one we
- // have has a stack that includes the chrome code that threw it, and in
- // particular has the wrong file/line/column information.
- JS::Rooted reflector(aCx);
- if (!domException->Sanitize(aCx, &reflector)) {
- // Well, that threw _an_ exception. Let's forget ours. We can just
- // unroot and not change the value, since mJSException is completely
- // ignored if mResult is not NS_ERROR_DOM_JS_EXCEPTION and we plan to
- // change mResult to a different value.
- js::RemoveRawValueRoot(aCx, &mJSException);
-
- // We no longer have a useful exception but we do want to signal that an
- // error occured.
- mResult = NS_ERROR_FAILURE;
-
- // But do make sure to not ReportJSException here, since we don't have one.
- return;
- }
-
- mJSException = reflector;
ReportJSException(aCx);
return;
}
@@ -307,17 +288,6 @@ ErrorResult::StealJSException(JSContext* cx,
value.set(mJSException);
js::RemoveRawValueRoot(cx, &mJSException);
mResult = NS_OK;
-
- if (value.isObject()) {
- // If it's a DOMException we may need to sanitize it.
- dom::DOMException* domException;
- nsresult rv =
- UNWRAP_OBJECT(DOMException, &value.toObject(), domException);
- if (NS_SUCCEEDED(rv) && !domException->Sanitize(cx, value)) {
- JS_GetPendingException(cx, value);
- JS_ClearPendingException(cx);
- }
- }
}
void
@@ -1813,8 +1783,8 @@ ReparentWrapper(JSContext* aCx, JS::Handle aObjArg)
// CloneExpandoChain() will use this property of |newobj| when it calls
// preserveWrapper() via attachExpandoObject() if |aObj| has expandos set, and
// preserveWrapper() will not do anything in this case. This is safe because
- // if expandos are present then the wrapper will already already have been
- // preserved called for this native.
+ // if expandos are present then the wrapper will already have been preserved
+ // for this native.
if (!xpc::XrayUtils::CloneExpandoChain(aCx, newobj, aObj)) {
return NS_ERROR_FAILURE;
}
@@ -2328,12 +2298,20 @@ bool
ResolveGlobal(JSContext* aCx, JS::Handle aObj,
JS::Handle aId, bool* aResolvedp)
{
+ MOZ_ASSERT(JS_IsGlobalObject(aObj),
+ "Should have a global here, since we plan to resolve standard "
+ "classes!");
+
return JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp);
}
bool
EnumerateGlobal(JSContext* aCx, JS::Handle aObj)
{
+ MOZ_ASSERT(JS_IsGlobalObject(aObj),
+ "Should have a global here, since we plan to enumerate standard "
+ "classes!");
+
return JS_EnumerateStandardClasses(aCx, aObj);
}
@@ -2727,5 +2705,28 @@ UnwrapArgImpl(JS::Handle src,
return wrappedJS->QueryInterface(iid, ppArg);
}
+bool
+SystemGlobalResolve(JSContext* cx, JS::Handle obj,
+ JS::Handle id, bool* resolvedp)
+{
+ if (!ResolveGlobal(cx, obj, id, resolvedp)) {
+ return false;
+ }
+
+ if (*resolvedp) {
+ return true;
+ }
+
+ return ResolveSystemBinding(cx, obj, id, resolvedp);
+}
+
+bool
+SystemGlobalEnumerate(JSContext* cx, JS::Handle obj)
+{
+ bool ignored = false;
+ return EnumerateGlobal(cx, obj) &&
+ ResolveSystemBinding(cx, obj, JSID_VOIDHANDLE, &ignored);
+}
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h
index 675a120c08e0..4b4282a5ff0a 100644
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -3270,6 +3270,18 @@ GetErrorPrototype(JSContext* aCx, JS::Handle aForObj)
return JS_GetErrorPrototype(aCx);
}
+// Resolve an id on the given global object that wants to be included in
+// Exposed=System webidl annotations. False return value means exception
+// thrown.
+bool SystemGlobalResolve(JSContext* cx, JS::Handle obj,
+ JS::Handle id, bool* resolvedp);
+
+// Enumerate all ids on the given global object that wants to be included in
+// Exposed=System webidl annotations. False return value means exception
+// thrown.
+bool SystemGlobalEnumerate(JSContext* cx, JS::Handle obj);
+
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
index 75e332391aac..845e1c8e677e 100644
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1270,6 +1270,11 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::TextTrackRegion',
},
+'WindowClient': {
+ 'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient',
+ 'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h',
+},
+
'WebGLActiveInfo': {
'nativeType': 'mozilla::WebGLActiveInfo',
'headerFile': 'WebGLActiveInfo.h'
diff --git a/dom/bindings/ErrorResult.h b/dom/bindings/ErrorResult.h
index 26f64ae3dbb6..64f977762b7a 100644
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -107,8 +107,7 @@ public:
// StealJSException steals the JS Exception from the object. This method must
// be called only if IsJSException() returns true. This method also resets the
- // ErrorCode() to NS_OK. The value will be ensured to be sanitized wrt to the
- // current compartment of cx if it happens to be a DOMException.
+ // ErrorCode() to NS_OK.
void StealJSException(JSContext* cx, JS::MutableHandle value);
void MOZ_ALWAYS_INLINE MightThrowJSException()
diff --git a/dom/bindings/Exceptions.cpp b/dom/bindings/Exceptions.cpp
index 6e59f9168e98..414d3f384546 100644
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -307,9 +307,6 @@ public:
NS_IMETHOD GetName(nsAString& aFunction) MOZ_OVERRIDE;
NS_IMETHOD GetCaller(nsIStackFrame** aCaller) MOZ_OVERRIDE;
NS_IMETHOD GetFormattedStack(nsAString& aStack) MOZ_OVERRIDE;
- virtual bool CallerSubsumes(JSContext* aCx) MOZ_OVERRIDE;
- NS_IMETHOD GetSanitized(JSContext* aCx,
- nsIStackFrame** aSanitized) MOZ_OVERRIDE;
protected:
virtual bool IsJSFrame() const MOZ_OVERRIDE {
@@ -391,37 +388,68 @@ NS_IMETHODIMP JSStackFrame::GetLanguageName(nsACString& aLanguageName)
return NS_OK;
}
+// Helper method to get the value of a stack property, if it's not already
+// cached. This will make sure we skip the cache if the access is happening
+// over Xrays.
+//
+// @argument aStack the stack we're working with; must be non-null.
+// @argument aPropGetter the getter function to call.
+// @argument aIsCached whether we've cached this property's value before.
+//
+// @argument [out] aCanCache whether the value can get cached.
+// @argument [out] aUseCachedValue if true, just use the cached value.
+// @argument [out] aValue the value we got from the stack.
+template
+static void
+GetValueIfNotCached(JSContext* aCx, JSObject* aStack,
+ JS::SavedFrameResult (*aPropGetter)(JSContext*,
+ JS::Handle,
+ GetterOutParamType),
+ bool aIsCached, bool* aCanCache, bool* aUseCachedValue,
+ ReturnType aValue)
+{
+ MOZ_ASSERT(aStack);
+
+ JS::Rooted stack(aCx, aStack);
+ // Allow caching if aCx and stack are same-compartment. Otherwise take the
+ // slow path.
+ *aCanCache = js::GetContextCompartment(aCx) == js::GetObjectCompartment(stack);
+ if (*aCanCache && aIsCached) {
+ *aUseCachedValue = true;
+ return;
+ }
+
+ *aUseCachedValue = false;
+ JS::ExposeObjectToActiveJS(stack);
+
+ aPropGetter(aCx, stack, aValue);
+}
+
/* readonly attribute AString filename; */
NS_IMETHODIMP JSStackFrame::GetFilename(nsAString& aFilename)
{
- // We can get called after unlink; in that case we can't do much
- // about producing a useful value.
- if (!mFilenameInitialized && mStack) {
- ThreadsafeAutoJSContext cx;
- JS::Rooted stack(cx, mStack);
- JS::ExposeObjectToActiveJS(mStack);
- JSAutoCompartment ac(cx, stack);
+ NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
+ ThreadsafeAutoJSContext cx;
+ JS::Rooted filename(cx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(cx, mStack, JS::GetSavedFrameSource, mFilenameInitialized,
+ &canCache, &useCachedValue, &filename);
+ if (useCachedValue) {
+ return StackFrame::GetFilename(aFilename);
+ }
- JS::Rooted filenameVal(cx);
- if (!JS_GetProperty(cx, stack, "source", &filenameVal)) {
- return NS_ERROR_UNEXPECTED;
- }
-
- if (filenameVal.isNull()) {
- filenameVal = JS_GetEmptyStringValue(cx);
- }
- MOZ_ASSERT(filenameVal.isString());
-
- nsAutoJSString str;
- if (!str.init(cx, filenameVal.toString())) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
+ nsAutoJSString str;
+ if (!str.init(cx, filename)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aFilename = str;
+ if (canCache) {
mFilename = str;
mFilenameInitialized = true;
}
- return StackFrame::GetFilename(aFilename);
+ return NS_OK;
}
NS_IMETHODIMP StackFrame::GetFilename(nsAString& aFilename)
@@ -439,30 +467,34 @@ NS_IMETHODIMP StackFrame::GetFilename(nsAString& aFilename)
/* readonly attribute AString name; */
NS_IMETHODIMP JSStackFrame::GetName(nsAString& aFunction)
{
- // We can get called after unlink; in that case we can't do much
- // about producing a useful value.
- if (!mFunnameInitialized && mStack) {
- ThreadsafeAutoJSContext cx;
- JS::Rooted stack(cx, mStack);
- JS::ExposeObjectToActiveJS(mStack);
- JSAutoCompartment ac(cx, stack);
- JS::Rooted nameVal(cx);
- // functionDisplayName can be null
- if (!JS_GetProperty(cx, stack, "functionDisplayName", &nameVal) ||
- (!nameVal.isString() && !nameVal.isNull())) {
- return NS_ERROR_UNEXPECTED;
- }
- if (nameVal.isString()) {
- nsAutoJSString str;
- if (!str.init(cx, nameVal.toString())) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- mFunname = str;
+ NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
+ ThreadsafeAutoJSContext cx;
+ JS::Rooted name(cx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(cx, mStack, JS::GetSavedFrameFunctionDisplayName,
+ mFunnameInitialized, &canCache, &useCachedValue,
+ &name);
+
+ if (useCachedValue) {
+ return StackFrame::GetName(aFunction);
+ }
+
+ if (name) {
+ nsAutoJSString str;
+ if (!str.init(cx, name)) {
+ return NS_ERROR_OUT_OF_MEMORY;
}
+ aFunction = str;
+ } else {
+ aFunction.SetIsVoid(true);
+ }
+
+ if (canCache) {
+ mFunname = aFunction;
mFunnameInitialized = true;
}
- return StackFrame::GetName(aFunction);
+ return NS_OK;
}
NS_IMETHODIMP StackFrame::GetName(nsAString& aFunction)
@@ -481,27 +513,25 @@ NS_IMETHODIMP StackFrame::GetName(nsAString& aFunction)
nsresult
JSStackFrame::GetLineno(int32_t* aLineNo)
{
- // We can get called after unlink; in that case we can't do much
- // about producing a useful value.
- if (!mLinenoInitialized && mStack) {
- ThreadsafeAutoJSContext cx;
- JS::Rooted stack(cx, mStack);
- JS::ExposeObjectToActiveJS(mStack);
- JSAutoCompartment ac(cx, stack);
- JS::Rooted lineVal(cx);
- if (!JS_GetProperty(cx, stack, "line", &lineVal)) {
- return NS_ERROR_UNEXPECTED;
- }
- if (lineVal.isNumber()) {
- mLineno = lineVal.toNumber();
- } else {
- MOZ_ASSERT(lineVal.isNull());
- mLineno = 0;
- }
+ NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
+ ThreadsafeAutoJSContext cx;
+ uint32_t line;
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(cx, mStack, JS::GetSavedFrameLine, mLinenoInitialized,
+ &canCache, &useCachedValue, &line);
+
+ if (useCachedValue) {
+ return StackFrame::GetLineno(aLineNo);
+ }
+
+ *aLineNo = line;
+
+ if (canCache) {
+ mLineno = line;
mLinenoInitialized = true;
}
- return StackFrame::GetLineno(aLineNo);
+ return NS_OK;
}
/* readonly attribute int32_t lineNumber; */
@@ -514,27 +544,25 @@ NS_IMETHODIMP StackFrame::GetLineNumber(int32_t* aLineNumber)
nsresult
JSStackFrame::GetColNo(int32_t* aColNo)
{
- // We can get called after unlink; in that case we can't do much
- // about producing a useful value.
- if (!mColNoInitialized && mStack) {
- ThreadsafeAutoJSContext cx;
- JS::Rooted stack(cx, mStack);
- JS::ExposeObjectToActiveJS(mStack);
- JSAutoCompartment ac(cx, stack);
- JS::Rooted colVal(cx);
- if (!JS_GetProperty(cx, stack, "column", &colVal)) {
- return NS_ERROR_UNEXPECTED;
- }
- if (colVal.isNumber()) {
- mColNo = colVal.toNumber();
- } else {
- MOZ_ASSERT(colVal.isNull());
- mColNo = 0;
- }
+ NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
+ ThreadsafeAutoJSContext cx;
+ uint32_t col;
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(cx, mStack, JS::GetSavedFrameColumn, mColNoInitialized,
+ &canCache, &useCachedValue, &col);
+
+ if (useCachedValue) {
+ return StackFrame::GetColNo(aColNo);
+ }
+
+ *aColNo = col;
+
+ if (canCache) {
+ mColNo = col;
mColNoInitialized = true;
}
- return StackFrame::GetColNo(aColNo);
+ return NS_OK;
}
/* readonly attribute int32_t columnNumber; */
@@ -550,63 +578,37 @@ NS_IMETHODIMP StackFrame::GetSourceLine(nsACString& aSourceLine)
return NS_OK;
}
-/* [noscript] readonly attribute nsIStackFrame sanitized */
-NS_IMETHODIMP StackFrame::GetSanitized(JSContext*, nsIStackFrame** aSanitized)
-{
- NS_ADDREF(*aSanitized = this);
- return NS_OK;
-}
-
-/* [noscript] readonly attribute nsIStackFrame sanitized */
-NS_IMETHODIMP JSStackFrame::GetSanitized(JSContext* aCx, nsIStackFrame** aSanitized)
-{
- // NB: Do _not_ enter the compartment of the SavedFrame object here, because
- // we are checking against the caller's compartment's principals in
- // GetFirstSubsumedSavedFrame.
-
- JS::RootedObject savedFrame(aCx, mStack);
- JS::ExposeObjectToActiveJS(mStack);
-
- savedFrame = js::GetFirstSubsumedSavedFrame(aCx, savedFrame);
- nsCOMPtr stackFrame;
- if (savedFrame) {
- stackFrame = new JSStackFrame(savedFrame);
- } else {
- stackFrame = new StackFrame();
- }
-
- stackFrame.forget(aSanitized);
- return NS_OK;
-}
-
/* readonly attribute nsIStackFrame caller; */
NS_IMETHODIMP JSStackFrame::GetCaller(nsIStackFrame** aCaller)
{
- // We can get called after unlink; in that case we can't do much
- // about producing a useful value.
- if (!mCallerInitialized && mStack) {
- ThreadsafeAutoJSContext cx;
- JS::Rooted stack(cx, mStack);
- JS::ExposeObjectToActiveJS(mStack);
- JSAutoCompartment ac(cx, stack);
- JS::Rooted callerVal(cx);
- if (!JS_GetProperty(cx, stack, "parent", &callerVal) ||
- !callerVal.isObjectOrNull()) {
- return NS_ERROR_UNEXPECTED;
- }
+ NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
+ ThreadsafeAutoJSContext cx;
+ JS::Rooted callerObj(cx);
+ bool canCache = false, useCachedValue = false;
+ GetValueIfNotCached(cx, mStack, JS::GetSavedFrameParent, mCallerInitialized,
+ &canCache, &useCachedValue, &callerObj);
- if (callerVal.isObject()) {
- JS::Rooted caller(cx, &callerVal.toObject());
- mCaller = new JSStackFrame(caller);
- } else {
- // Do we really need this dummy frame? If so, we should document why... I
- // guess for symmetry with the "nothing on the stack" case, which returns
- // a single dummy frame?
- mCaller = new StackFrame();
- }
+ if (useCachedValue) {
+ return StackFrame::GetCaller(aCaller);
+ }
+
+ nsCOMPtr caller;
+ if (callerObj) {
+ caller = new JSStackFrame(callerObj);
+ } else {
+ // Do we really need this dummy frame? If so, we should document why... I
+ // guess for symmetry with the "nothing on the stack" case, which returns
+ // a single dummy frame?
+ caller = new StackFrame();
+ }
+ caller.forget(aCaller);
+
+ if (canCache) {
+ mCaller = *aCaller;
mCallerInitialized = true;
}
- return StackFrame::GetCaller(aCaller);
+
+ return NS_OK;
}
NS_IMETHODIMP StackFrame::GetCaller(nsIStackFrame** aCaller)
@@ -617,26 +619,42 @@ NS_IMETHODIMP StackFrame::GetCaller(nsIStackFrame** aCaller)
NS_IMETHODIMP JSStackFrame::GetFormattedStack(nsAString& aStack)
{
- // We can get called after unlink; in that case we can't do much
- // about producing a useful value.
- if (!mFormattedStackInitialized && mStack) {
- ThreadsafeAutoJSContext cx;
- JS::Rooted stack(cx, JS::ObjectValue(*mStack));
- JS::ExposeObjectToActiveJS(mStack);
- JSAutoCompartment ac(cx, mStack);
- JS::Rooted formattedStack(cx, JS::ToString(cx, stack));
- if (!formattedStack) {
- return NS_ERROR_UNEXPECTED;
- }
- nsAutoJSString str;
- if (!str.init(cx, formattedStack)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
+ NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
+ // Sadly we can't use GetValueIfNotCached here, because our getter
+ // returns bool, not JS::SavedFrameResult. Maybe it's possible to
+ // make the templates more complicated to deal, but in the meantime
+ // let's just inline GetValueIfNotCached here.
+ ThreadsafeAutoJSContext cx;
+
+ // Allow caching if cx and stack are same-compartment. Otherwise take the
+ // slow path.
+ bool canCache =
+ js::GetContextCompartment(cx) == js::GetObjectCompartment(mStack);
+ if (canCache && mFormattedStackInitialized) {
+ aStack = mFormattedStack;
+ return NS_OK;
+ }
+
+ JS::ExposeObjectToActiveJS(mStack);
+ JS::Rooted stack(cx, mStack);
+
+ JS::Rooted formattedStack(cx);
+ if (!JS::StringifySavedFrameStack(cx, stack, &formattedStack)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoJSString str;
+ if (!str.init(cx, formattedStack)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ aStack = str;
+
+ if (canCache) {
mFormattedStack = str;
mFormattedStackInitialized = true;
}
- aStack = mFormattedStack;
return NS_OK;
}
@@ -681,37 +699,6 @@ NS_IMETHODIMP StackFrame::ToString(nsACString& _retval)
return NS_OK;
}
-/* virtual */ bool
-StackFrame::CallerSubsumes(JSContext* aCx)
-{
- return true;
-}
-
-/* virtual */ bool
-JSStackFrame::CallerSubsumes(JSContext* aCx)
-{
- if (!NS_IsMainThread()) {
- return true;
- }
-
- if (!mStack) {
- // No problem here, there's no data to leak.
- return true;
- }
-
- nsIPrincipal* callerPrincipal = nsContentUtils::SubjectPrincipal();
-
- JS::Rooted unwrappedStack(aCx, js::CheckedUnwrap(mStack));
- if (!unwrappedStack) {
- // We can't leak data here either.
- return true;
- }
-
- nsIPrincipal* stackPrincipal =
- nsJSPrincipals::get(js::GetSavedFramePrincipals(unwrappedStack));
- return callerPrincipal->SubsumesConsideringDomain(stackPrincipal);
-}
-
/* static */ already_AddRefed
JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth)
{
diff --git a/dom/browser-element/BrowserElementChild.js b/dom/browser-element/BrowserElementChild.js
index d93b2002d204..6bf1c5f80b33 100644
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -42,20 +42,24 @@ if (!('BrowserElementIsPreloaded' in this)) {
}
}
- if (docShell.asyncPanZoomEnabled === false) {
- Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js");
- ContentPanningAPZDisabled.init();
- }
+ if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") == 1) {
+ if (docShell.asyncPanZoomEnabled === false) {
+ Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js");
+ ContentPanningAPZDisabled.init();
+ }
- Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
- ContentPanning.init();
+ Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
+ ContentPanning.init();
+ }
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
} else {
- if (docShell.asyncPanZoomEnabled === false) {
- ContentPanningAPZDisabled.init();
+ if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") == 1) {
+ if (docShell.asyncPanZoomEnabled === false) {
+ ContentPanningAPZDisabled.init();
+ }
+ ContentPanning.init();
}
- ContentPanning.init();
}
var BrowserElementIsReady = true;
diff --git a/dom/cache/CacheStorageChild.cpp b/dom/cache/CacheStorageChild.cpp
index 54d1265f2349..3fce81b84ee3 100644
--- a/dom/cache/CacheStorageChild.cpp
+++ b/dom/cache/CacheStorageChild.cpp
@@ -137,7 +137,9 @@ CacheStorageChild::RecvOpenResponse(const RequestId& aRequestId,
// the feature won't try to shutdown the actor until after we create the
// Cache DOM object in the listener's RecvOpenResponse() method. This
// is important because StartShutdown() expects a Cache object listener.
- cacheChild->SetFeature(GetFeature());
+ if (cacheChild) {
+ cacheChild->SetFeature(GetFeature());
+ }
listener->RecvOpenResponse(aRequestId, aRv, cacheChild);
return true;
diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp
index 03da25a38446..6acf83e19dcc 100644
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -339,7 +339,7 @@ DBSchema::CachePut(mozIStorageConnection* aConn, CacheId aCacheId,
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
- PCacheQueryParams params(false, false, false, false, false,
+ PCacheQueryParams params(false, false, false, false,
NS_LITERAL_STRING(""));
nsAutoTArray matches;
nsresult rv = QueryCache(aConn, aCacheId, aRequest, params, matches);
@@ -675,13 +675,7 @@ DBSchema::QueryCache(mozIStorageConnection* aConn, CacheId aCacheId,
query.AppendLiteral("request_url");
}
- if (aParams.prefixMatch()) {
- query.AppendLiteral(" LIKE ?2 ESCAPE '\\'");
- } else {
- query.AppendLiteral("=?2");
- }
-
- query.AppendLiteral(" GROUP BY entries.id ORDER BY entries.id;");
+ query.AppendLiteral("=?2 GROUP BY entries.id ORDER BY entries.id;");
nsCOMPtr state;
nsresult rv = aConn->CreateStatement(query, getter_AddRefs(state));
@@ -690,14 +684,6 @@ DBSchema::QueryCache(mozIStorageConnection* aConn, CacheId aCacheId,
rv = state->BindInt32Parameter(0, aCacheId);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
- if (aParams.prefixMatch()) {
- nsAutoString escapedUrlToMatch;
- rv = state->EscapeStringForLIKE(urlToMatch, '\\', escapedUrlToMatch);
- if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
- urlToMatch = escapedUrlToMatch;
- urlToMatch.AppendLiteral("%");
- }
-
rv = state->BindStringParameter(1, urlToMatch);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
diff --git a/dom/cache/PCacheTypes.ipdlh b/dom/cache/PCacheTypes.ipdlh
index 03a6b6e6330a..d56ca86e647a 100644
--- a/dom/cache/PCacheTypes.ipdlh
+++ b/dom/cache/PCacheTypes.ipdlh
@@ -22,7 +22,6 @@ struct PCacheQueryParams
bool ignoreSearch;
bool ignoreMethod;
bool ignoreVary;
- bool prefixMatch;
bool cacheNameSet;
nsString cacheName;
};
diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp
index ff8d9ad34b66..05e6e5a0ec77 100644
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -266,7 +266,6 @@ TypeUtils::ToPCacheQueryParams(PCacheQueryParams& aOut,
aOut.ignoreSearch() = aIn.mIgnoreSearch;
aOut.ignoreMethod() = aIn.mIgnoreMethod;
aOut.ignoreVary() = aIn.mIgnoreVary;
- aOut.prefixMatch() = aIn.mPrefixMatch;
aOut.cacheNameSet() = aIn.mCacheName.WasPassed();
if (aOut.cacheNameSet()) {
aOut.cacheName() = aIn.mCacheName.Value();
diff --git a/dom/events/DragEvent.cpp b/dom/events/DragEvent.cpp
index b507c5d27464..0a5c5caefd04 100644
--- a/dom/events/DragEvent.cpp
+++ b/dom/events/DragEvent.cpp
@@ -131,6 +131,26 @@ DragEvent::GetDataTransfer()
return dragEvent->dataTransfer;
}
+// static
+already_AddRefed
+DragEvent::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const DragEventInit& aParam,
+ ErrorResult& aRv)
+{
+ nsCOMPtr t = do_QueryInterface(aGlobal.GetAsSupports());
+ nsRefPtr e = new DragEvent(t, nullptr, nullptr);
+ bool trusted = e->Init(t);
+ aRv = e->InitDragEvent(aType, aParam.mBubbles, aParam.mCancelable,
+ aParam.mView, aParam.mDetail, aParam.mScreenX,
+ aParam.mScreenY, aParam.mClientX, aParam.mClientY,
+ aParam.mCtrlKey, aParam.mAltKey, aParam.mShiftKey,
+ aParam.mMetaKey, aParam.mButton, aParam.mRelatedTarget,
+ aParam.mDataTransfer);
+ e->SetTrusted(trusted);
+ return e.forget();
+}
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/events/DragEvent.h b/dom/events/DragEvent.h
index 77aad6f653c5..e4aed6f9fcff 100644
--- a/dom/events/DragEvent.h
+++ b/dom/events/DragEvent.h
@@ -48,6 +48,11 @@ public:
DataTransfer* aDataTransfer,
ErrorResult& aError);
+ static already_AddRefed Constructor(const GlobalObject& aGlobal,
+ const nsAString& aType,
+ const DragEventInit& aParam,
+ ErrorResult& aRv);
+
protected:
~DragEvent() {}
};
diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp
index 2c80d449c376..0e267f35804d 100644
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2417,13 +2417,16 @@ EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
actualDevPixelScrollAmount.y = 0;
}
+ nsIScrollableFrame::ScrollSnapMode snapMode = nsIScrollableFrame::DISABLE_SNAP;
nsIAtom* origin = nullptr;
switch (aEvent->deltaMode) {
case nsIDOMWheelEvent::DOM_DELTA_LINE:
origin = nsGkAtoms::mouseWheel;
+ snapMode = nsIScrollableFrame::ENABLE_SNAP;
break;
case nsIDOMWheelEvent::DOM_DELTA_PAGE:
origin = nsGkAtoms::pages;
+ snapMode = nsIScrollableFrame::ENABLE_SNAP;
break;
case nsIDOMWheelEvent::DOM_DELTA_PIXEL:
origin = nsGkAtoms::pixels;
@@ -2476,10 +2479,14 @@ EventStateManager::DoScrollText(nsIScrollableFrame* aScrollableFrame,
MOZ_CRASH("Invalid scrollType value comes");
}
+ nsIScrollableFrame::ScrollMomentum momentum =
+ aEvent->isMomentum ? nsIScrollableFrame::SYNTHESIZED_MOMENTUM_EVENT
+ : nsIScrollableFrame::NOT_MOMENTUM;
+
nsIntPoint overflow;
aScrollableFrame->ScrollBy(actualDevPixelScrollAmount,
nsIScrollableFrame::DEVICE_PIXELS,
- mode, &overflow, origin, aEvent->isMomentum);
+ mode, &overflow, origin, momentum, snapMode);
if (!scrollFrameWeak.IsAlive()) {
// If the scroll causes changing the layout, we can think that the event
@@ -2980,6 +2987,13 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
{
MOZ_ASSERT(aEvent->mFlags.mIsTrusted);
ScrollbarsForWheel::MayInactivate();
+ WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent();
+ nsIScrollableFrame* scrollTarget =
+ ComputeScrollTarget(aTargetFrame, wheelEvent,
+ COMPUTE_DEFAULT_ACTION_TARGET);
+ if (scrollTarget) {
+ scrollTarget->ScrollSnap();
+ }
}
break;
case NS_WHEEL_WHEEL:
diff --git a/dom/events/test/test_eventctors.html b/dom/events/test/test_eventctors.html
index e1d1062f151a..3f7e4bcb8e49 100644
--- a/dom/events/test/test_eventctors.html
+++ b/dom/events/test/test_eventctors.html
@@ -824,6 +824,21 @@ while (testWheelProps.length) {
}
}
+// DragEvent
+
+try {
+ e = new DragEvent();
+} catch(exp) {
+ ex = true;
+}
+ok(ex, "DragEvent: First parameter is required!");
+ex = false;
+
+e = new DragEvent("hello");
+is(e.type, "hello", "DragEvent: Wrong event type!");
+document.dispatchEvent(e);
+is(receivedEvent, e, "DragEvent: Wrong event!");
+
// TransitionEvent
e = new TransitionEvent("hello", { propertyName: "color", elapsedTime: 3.5, pseudoElement: "", foobar: "baz" })
is("propertyName" in e, true, "Transition events have propertyName property");
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
index 0b7ca44bc593..1be0d9565d35 100644
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -177,7 +177,7 @@ public:
nsRefPtr fetch = new FetchDriver(mRequest, principal, loadGroup);
nsIDocument* doc = mResolver->GetWorkerPrivate()->GetDocument();
if (doc) {
- fetch->SetReferrerPolicy(doc->GetReferrerPolicy());
+ fetch->SetDocument(doc);
}
nsresult rv = fetch->Fetch(mResolver);
@@ -234,7 +234,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
nsCOMPtr loadGroup = doc->GetDocumentLoadGroup();
nsRefPtr fetch =
new FetchDriver(r, doc->NodePrincipal(), loadGroup);
- fetch->SetReferrerPolicy(doc->GetReferrerPolicy());
+ fetch->SetDocument(doc);
aRv = fetch->Fetch(resolver);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@@ -315,7 +315,6 @@ public:
result.ThrowTypeError(MSG_FETCH_FAILED);
promise->MaybeReject(result);
}
-
return true;
}
};
diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp
index 6d94a708341a..7a5938b9628f 100644
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -5,6 +5,7 @@
#include "mozilla/dom/FetchDriver.h"
+#include "nsIDocument.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIHttpChannel.h"
@@ -42,7 +43,6 @@ FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
, mLoadGroup(aLoadGroup)
, mRequest(aRequest)
, mFetchRecursionCount(0)
- , mReferrerPolicy(net::RP_Default)
, mResponseAvailableCalled(false)
{
}
@@ -99,11 +99,27 @@ FetchDriver::ContinueFetch(bool aCORSFlag)
return FailWithNetworkError();
}
+ // CSP/mixed content checks.
+ int16_t shouldLoad;
+ rv = NS_CheckContentLoadPolicy(mRequest->ContentPolicyType(),
+ requestURI,
+ mPrincipal,
+ mDocument,
+ // FIXME(nsm): Should MIME be extracted from
+ // Content-Type header?
+ EmptyCString(), /* mime guess */
+ nullptr, /* extra */
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) {
+ // Disallowed by content policy.
+ return FailWithNetworkError();
+ }
+
// Begin Step 4 of the Fetch algorithm
// https://fetch.spec.whatwg.org/#fetching
- // FIXME(nsm): Bug 1039846: Add CSP checks
-
nsAutoCString scheme;
rv = requestURI->GetScheme(scheme);
if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -289,7 +305,6 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
{
// Step 1. "Let response be null."
mResponse = nullptr;
-
nsresult rv;
nsCOMPtr ios = do_GetIOService(&rv);
@@ -406,7 +421,12 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
return FailWithNetworkError();
}
- rv = httpChan->SetReferrerWithPolicy(refURI, mReferrerPolicy);
+ net::ReferrerPolicy referrerPolicy = net::RP_Default;
+ if (mDocument) {
+ referrerPolicy = mDocument->GetReferrerPolicy();
+ }
+
+ rv = httpChan->SetReferrerWithPolicy(refURI, referrerPolicy);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
@@ -869,5 +889,13 @@ FetchDriver::OnRedirectVerifyCallback(nsresult aResult)
mRedirectCallback = nullptr;
return NS_OK;
}
+
+void
+FetchDriver::SetDocument(nsIDocument* aDocument)
+{
+ // Cannot set document after Fetch() has been called.
+ MOZ_ASSERT(mFetchRecursionCount == 0);
+ mDocument = aDocument;
+}
} // namespace dom
} // namespace mozilla
diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h
index 63740a9a5025..f39a0ed31094 100644
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -16,6 +16,7 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/net/ReferrerPolicy.h"
+class nsIDocument;
class nsIOutputStream;
class nsILoadGroup;
class nsIPrincipal;
@@ -58,12 +59,7 @@ public:
NS_IMETHOD Fetch(FetchDriverObserver* aObserver);
void
- SetReferrerPolicy(net::ReferrerPolicy aPolicy)
- {
- // Cannot set policy after Fetch() has been called.
- MOZ_ASSERT(mFetchRecursionCount == 0);
- mReferrerPolicy = aPolicy;
- }
+ SetDocument(nsIDocument* aDocument);
private:
nsCOMPtr mPrincipal;
@@ -76,8 +72,8 @@ private:
nsCOMPtr mRedirectCallback;
nsCOMPtr mOldRedirectChannel;
nsCOMPtr mNewRedirectChannel;
+ nsCOMPtr mDocument;
uint32_t mFetchRecursionCount;
- net::ReferrerPolicy mReferrerPolicy;
DebugOnly mResponseAvailableCalled;
diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp
index df59d3d99b87..f1ad3373422a 100644
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -561,8 +561,9 @@ HTMLCanvasElement::ToBlob(JSContext* aCx,
nsresult rv = blob->GetSize(&size);
if (NS_SUCCEEDED(rv)) {
AutoJSAPI jsapi;
- jsapi.Init(mGlobal);
- JS_updateMallocCounter(jsapi.cx(), size);
+ if (jsapi.Init(mGlobal)) {
+ JS_updateMallocCounter(jsapi.cx(), size);
+ }
}
nsRefPtr newBlob = new File(mGlobal, blob->Impl());
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index 6001a651ae3c..8e76c1ff3126 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1544,11 +1544,10 @@ ContentChild::DeallocPNeckoChild(PNeckoChild* necko)
PPrintingChild*
ContentChild::AllocPPrintingChild()
{
- // The ContentParent should never attempt to allocate the
- // nsPrintingPromptServiceProxy, which implements PPrintingChild. Instead,
- // the nsPrintingPromptServiceProxy service is requested and instantiated
- // via XPCOM, and the constructor of nsPrintingPromptServiceProxy sets up
- // the IPC connection.
+ // The ContentParent should never attempt to allocate the nsPrintingProxy,
+ // which implements PPrintingChild. Instead, the nsPrintingProxy service is
+ // requested and instantiated via XPCOM, and the constructor of
+ // nsPrintingProxy sets up the IPC connection.
NS_NOTREACHED("Should never get here!");
return nullptr;
}
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 04de1b007b51..dd701dd1228e 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -513,6 +513,7 @@ child:
// The following methods correspond to functions on the GeckoContentController
// interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
// in that file for these functions.
+ RequestFlingSnap(ViewID aScrollID, CSSPoint aDestination);
AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
HandleDoubleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
HandleSingleTap(CSSPoint aPoint, Modifiers aModifiers, ScrollableLayerGuid aGuid);
diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp
index ebfb8666813c..9381bde09d8e 100644
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2036,6 +2036,14 @@ TabChild::RecvUpdateFrame(const FrameMetrics& aFrameMetrics)
return TabChildBase::UpdateFrameHandler(aFrameMetrics);
}
+bool
+TabChild::RecvRequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination)
+{
+ APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
+ return true;
+}
+
bool
TabChild::RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
const uint32_t& aScrollGeneration)
diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h
index a8299dd5c8ab..135a5cf74a77 100644
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -319,6 +319,8 @@ public:
const ScreenOrientation& orientation,
const nsIntPoint& chromeDisp) MOZ_OVERRIDE;
virtual bool RecvUpdateFrame(const layers::FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
+ virtual bool RecvRequestFlingSnap(const ViewID& aScrollId,
+ const CSSPoint& aDestination) MOZ_OVERRIDE;
virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
const uint32_t& aScrollGeneration) MOZ_OVERRIDE;
virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint,
diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp
index 5abd4462d962..1e392f3dfc27 100644
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -922,6 +922,15 @@ TabParent::UIResolutionChanged()
}
}
+void
+TabParent::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination)
+{
+ if (!mIsDestroyed) {
+ unused << SendRequestFlingSnap(aScrollId, aDestination);
+ }
+}
+
void
TabParent::AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration)
{
@@ -2620,6 +2629,86 @@ TabParent::GetTabId(uint64_t* aId)
return NS_OK;
}
+class LayerTreeUpdateRunnable MOZ_FINAL
+ : public nsRunnable
+{
+ uint64_t mLayersId;
+ bool mActive;
+
+public:
+ explicit LayerTreeUpdateRunnable(uint64_t aLayersId, bool aActive)
+ : mLayersId(aLayersId), mActive(aActive) {}
+
+private:
+ NS_IMETHOD Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+ TabParent* tabParent = TabParent::GetTabParentFromLayersId(mLayersId);
+ if (tabParent) {
+ tabParent->LayerTreeUpdate(mActive);
+ }
+ return NS_OK;
+ }
+};
+
+// This observer runs on the compositor thread, so we dispatch a runnable to the
+// main thread to actually dispatch the event.
+class LayerTreeUpdateObserver : public CompositorUpdateObserver
+{
+ virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) {
+ nsRefPtr runnable = new LayerTreeUpdateRunnable(aLayersId, aActive);
+ NS_DispatchToMainThread(runnable);
+ }
+};
+
+bool
+TabParent::RequestNotifyLayerTreeReady()
+{
+ RenderFrameParent* frame = GetRenderFrame();
+ if (!frame) {
+ return false;
+ }
+
+ CompositorParent::RequestNotifyLayerTreeReady(frame->GetLayersId(),
+ new LayerTreeUpdateObserver());
+ return true;
+}
+
+bool
+TabParent::RequestNotifyLayerTreeCleared()
+{
+ RenderFrameParent* frame = GetRenderFrame();
+ if (!frame) {
+ return false;
+ }
+
+ CompositorParent::RequestNotifyLayerTreeCleared(frame->GetLayersId(),
+ new LayerTreeUpdateObserver());
+ return true;
+}
+
+bool
+TabParent::LayerTreeUpdate(bool aActive)
+{
+ nsCOMPtr target = do_QueryInterface(mFrameElement);
+ if (!target) {
+ NS_WARNING("Could not locate target for layer tree message.");
+ return true;
+ }
+
+ nsCOMPtr event;
+ NS_NewDOMEvent(getter_AddRefs(event), mFrameElement, nullptr, nullptr);
+ if (aActive) {
+ event->InitEvent(NS_LITERAL_STRING("MozLayerTreeReady"), true, false);
+ } else {
+ event->InitEvent(NS_LITERAL_STRING("MozLayerTreeCleared"), true, false);
+ }
+ event->SetTrusted(true);
+ event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
+ bool dummy;
+ mFrameElement->DispatchEvent(event, &dummy);
+ return true;
+}
+
bool
TabParent::RecvRemotePaintIsReady()
{
diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h
index 445cc996e506..75f690905017 100644
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -245,6 +245,8 @@ public:
const nsIntPoint& chromeDisp);
void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
void UIResolutionChanged();
+ void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination);
void AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration);
void HandleDoubleTap(const CSSPoint& aPoint,
Modifiers aModifiers,
@@ -372,6 +374,11 @@ public:
bool SendLoadRemoteScript(const nsString& aURL,
const bool& aRunInGlobalScope);
+ // See nsIFrameLoader requestNotifyLayerTreeReady.
+ bool RequestNotifyLayerTreeReady();
+ bool RequestNotifyLayerTreeCleared();
+ bool LayerTreeUpdate(bool aActive);
+
protected:
bool ReceiveMessage(const nsString& aMessage,
bool aSync,
diff --git a/dom/ipc/jar.mn b/dom/ipc/jar.mn
index c1d6647af2e6..d48ef3e61c8b 100644
--- a/dom/ipc/jar.mn
+++ b/dom/ipc/jar.mn
@@ -7,7 +7,7 @@ toolkit.jar:
content/global/remote-test-ipc.js (remote-test.js)
content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js)
-* content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js)
+ content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js)
* content/global/BrowserElementPanningAPZDisabled.js (../browser-element/BrowserElementPanningAPZDisabled.js)
content/global/preload.js (preload.js)
content/global/post-fork-preload.js (post-fork-preload.js)
diff --git a/dom/ipc/preload.js b/dom/ipc/preload.js
index de0cfd95d8f5..ff9330d28679 100644
--- a/dom/ipc/preload.js
+++ b/dom/ipc/preload.js
@@ -91,14 +91,17 @@ const BrowserElementIsPreloaded = true;
} catch (e) {
}
- try {
- if (Services.prefs.getBoolPref("layers.async-pan-zoom.enabled") === false) {
- Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js", global);
+ if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") == 1) {
+ try {
+ if (Services.prefs.getBoolPref("layers.async-pan-zoom.enabled") === false) {
+ Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js", global);
+ }
+ } catch (e) {
}
- } catch (e) {
+
+ Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js", global);
}
- Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js", global);
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js", global);
Services.io.getProtocolHandler("app");
diff --git a/dom/media/fmp4/wmf/MFTDecoder.cpp b/dom/media/fmp4/wmf/MFTDecoder.cpp
index 082afe4cb007..f706addd6d63 100644
--- a/dom/media/fmp4/wmf/MFTDecoder.cpp
+++ b/dom/media/fmp4/wmf/MFTDecoder.cpp
@@ -62,11 +62,6 @@ MFTDecoder::SetMediaTypes(IMFMediaType* aInputType,
hr = mDecoder->GetInputStreamInfo(0, &mInputStreamInfo);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
- hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
- NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
- mMFTProvidesOutputSamples = IsFlagSet(mOutputStreamInfo.dwFlags, MFT_OUTPUT_STREAM_PROVIDES_SAMPLES);
-
hr = SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
@@ -101,6 +96,12 @@ MFTDecoder::SetDecoderOutputType()
if (SUCCEEDED(hr) && resultMatch == TRUE) {
hr = mDecoder->SetOutputType(0, outputType, 0);
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ hr = mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo);
+ NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+ mMFTProvidesOutputSamples = IsFlagSet(mOutputStreamInfo.dwFlags, MFT_OUTPUT_STREAM_PROVIDES_SAMPLES);
+
return S_OK;
}
outputType = nullptr;
diff --git a/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp b/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
index 9cceca77e415..a596a6b34b05 100644
--- a/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
@@ -71,13 +71,11 @@ WMFVideoMFTManager::WMFVideoMFTManager(
mozilla::layers::LayersBackend aLayersBackend,
mozilla::layers::ImageContainer* aImageContainer,
bool aDXVAEnabled)
- : mVideoStride(0)
- , mVideoWidth(0)
- , mVideoHeight(0)
- , mImageContainer(aImageContainer)
+ : mImageContainer(aImageContainer)
, mDXVAEnabled(aDXVAEnabled)
, mLayersBackend(aLayersBackend)
- , mUseHwAccel(false)
+ // mVideoStride, mVideoWidth, mVideoHeight, mUseHwAccel are initialized in
+ // Init().
{
NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
MOZ_ASSERT(mImageContainer);
@@ -170,6 +168,7 @@ WMFVideoMFTManager::InitializeDXVA()
TemporaryRef
WMFVideoMFTManager::Init()
{
+ mUseHwAccel = false; // default value; changed if D3D setup succeeds.
bool useDxva = InitializeDXVA();
RefPtr decoder(new MFTDecoder());
@@ -228,6 +227,13 @@ WMFVideoMFTManager::Init()
mDecoder = decoder;
LOG("Video Decoder initialized, Using DXVA: %s", (mUseHwAccel ? "Yes" : "No"));
+ // Just in case ConfigureVideoFrameGeometry() does not set these
+ mVideoInfo = VideoInfo();
+ mVideoStride = 0;
+ mVideoWidth = 0;
+ mVideoHeight = 0;
+ mPictureRegion.SetEmpty();
+
return decoder.forget();
}
@@ -504,7 +510,7 @@ WMFVideoMFTManager::Shutdown()
bool
WMFVideoMFTManager::IsHardwareAccelerated() const
{
- return mUseHwAccel;
+ return mDecoder && mUseHwAccel;
}
} // namespace mozilla
diff --git a/dom/media/fmp4/wmf/WMFVideoMFTManager.h b/dom/media/fmp4/wmf/WMFVideoMFTManager.h
index acfd88dc5f10..a9ac2e9e80e0 100644
--- a/dom/media/fmp4/wmf/WMFVideoMFTManager.h
+++ b/dom/media/fmp4/wmf/WMFVideoMFTManager.h
@@ -61,8 +61,6 @@ private:
RefPtr mDecoder;
RefPtr mImageContainer;
nsAutoPtr mDXVA2Manager;
- RefPtr mTaskQueue;
- MediaDataDecoderCallback* mCallback;
const bool mDXVAEnabled;
const layers::LayersBackend mLayersBackend;
diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
index 60c7c90141fb..3ffef389e11e 100644
--- a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
@@ -41,7 +41,7 @@ tests.forEach(function(e) {
var measn = e.ac.createMediaElementSource(a);
var sp = e.ac.createScriptProcessor(2048, 1);
// Set a couple expandos to track the status of the test
- sp.iterationsLeft = 20;
+ sp.iterationsLeft = 200;
sp.seenSound = false;
measn.connect(sp);
diff --git a/dom/plugins/ipc/PluginAsyncSurrogate.cpp b/dom/plugins/ipc/PluginAsyncSurrogate.cpp
index d860de85761f..dbf293a9a921 100644
--- a/dom/plugins/ipc/PluginAsyncSurrogate.cpp
+++ b/dom/plugins/ipc/PluginAsyncSurrogate.cpp
@@ -172,11 +172,11 @@ PluginAsyncSurrogate::NP_GetEntryPoints(NPPluginFuncs* aFuncs)
aFuncs->setwindow = &NPP_SetWindow;
aFuncs->writeready = &NPP_WriteReady;
aFuncs->event = &NPP_HandleEvent;
+ aFuncs->destroystream = &NPP_DestroyStream;
// We need to set these so that content code doesn't make assumptions
// about these operations not being supported
aFuncs->write = &PluginModuleParent::NPP_Write;
aFuncs->asfile = &PluginModuleParent::NPP_StreamAsFile;
- aFuncs->destroystream = &PluginModuleParent::NPP_DestroyStream;
}
NPError
@@ -267,6 +267,19 @@ PluginAsyncSurrogate::NPP_WriteReady(NPStream* aStream)
return 0;
}
+NPError
+PluginAsyncSurrogate::NPP_DestroyStream(NPStream* aStream, NPReason aReason)
+{
+ for (uint32_t idx = 0, len = mPendingNewStreamCalls.Length(); idx < len; ++idx) {
+ PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[idx];
+ if (curPendingCall.mStream == aStream) {
+ mPendingNewStreamCalls.RemoveElementAt(idx);
+ break;
+ }
+ }
+ return NPERR_NO_ERROR;
+}
+
/* static */ NPError
PluginAsyncSurrogate::NPP_Destroy(NPP aInstance, NPSavedData** aSave)
{
@@ -350,6 +363,16 @@ PluginAsyncSurrogate::NPP_WriteReady(NPP aInstance, NPStream* aStream)
return surrogate->NPP_WriteReady(aStream);
}
+/* static */ NPError
+PluginAsyncSurrogate::NPP_DestroyStream(NPP aInstance,
+ NPStream* aStream,
+ NPReason aReason)
+{
+ PluginAsyncSurrogate* surrogate = Cast(aInstance);
+ MOZ_ASSERT(surrogate);
+ return surrogate->NPP_DestroyStream(aStream, aReason);
+}
+
PluginAsyncSurrogate::PendingNewStreamCall::PendingNewStreamCall(
NPMIMEType aType, NPStream* aStream, NPBool aSeekable)
: mType(NullableString(aType))
@@ -403,7 +426,7 @@ PluginAsyncSurrogate::OnInstanceCreated(PluginInstanceParent* aInstance)
&streamType);
if (curError != NPERR_NO_ERROR) {
// If we failed here then the send failed and we need to clean up
- parent::_destroystream(mInstance, curPendingCall.mStream, NPRES_DONE);
+ DestroyAsyncStream(curPendingCall.mStream);
}
}
mPendingNewStreamCalls.Clear();
diff --git a/dom/plugins/ipc/PluginAsyncSurrogate.h b/dom/plugins/ipc/PluginAsyncSurrogate.h
index 8000ba0b5843..b216b8f465b8 100644
--- a/dom/plugins/ipc/PluginAsyncSurrogate.h
+++ b/dom/plugins/ipc/PluginAsyncSurrogate.h
@@ -42,6 +42,7 @@ public:
void NPP_Print(NPPrint* aPrintInfo);
int16_t NPP_HandleEvent(void* aEvent);
int32_t NPP_WriteReady(NPStream* aStream);
+ NPError NPP_DestroyStream(NPStream* aStream, NPReason aReason);
void OnInstanceCreated(PluginInstanceParent* aInstance);
static bool Create(PluginModuleParent* aParent, NPMIMEType aPluginType,
NPP aInstance, uint16_t aMode, int16_t aArgc,
@@ -98,6 +99,8 @@ private:
static void NPP_Print(NPP aInstance, NPPrint* aPrintInfo);
static int16_t NPP_HandleEvent(NPP aInstance, void* aEvent);
static int32_t NPP_WriteReady(NPP aInstance, NPStream* aStream);
+ static NPError NPP_DestroyStream(NPP aInstance, NPStream* aStream,
+ NPReason aReason);
static NPObject* ScriptableAllocate(NPP aInstance, NPClass* aClass);
static void ScriptableInvalidate(NPObject* aObject);
diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp
index f3028b1bdc44..4e146640c246 100755
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -1436,11 +1436,7 @@ PluginModuleParent::NPP_DestroyStream(NPP instance,
NPStream* stream,
NPReason reason)
{
- PluginInstanceParent* i = PluginInstanceParent::Cast(instance);
- if (!i)
- return NPERR_GENERIC_ERROR;
-
- return i->NPP_DestroyStream(stream, reason);
+ RESOLVE_AND_CALL(instance, NPP_DestroyStream(stream, reason));
}
int32_t
diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp
index 8856e56dd74b..d9165e9d99c2 100644
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -212,11 +212,6 @@ protected:
if (rv.Failed()) {
JS::Rooted exn(cx);
if (rv.IsJSException()) {
- // Enter the compartment of mPromise before stealing the JS exception,
- // since the StealJSException call will use the current compartment for
- // a security check that determines how much of the stack we're allowed
- // to see and we'll be exposing that stack to consumers of mPromise.
- JSAutoCompartment ac(cx, mPromise->GlobalJSObject());
rv.StealJSException(cx, &exn);
} else {
// Convert the ErrorResult to a JS exception object that we can reject
@@ -610,14 +605,7 @@ Promise::CallInitFunction(const GlobalObject& aGlobal,
if (aRv.IsJSException()) {
JS::Rooted value(cx);
- { // scope for ac
- // Enter the compartment of our global before stealing the JS exception,
- // since the StealJSException call will use the current compartment for
- // a security check that determines how much of the stack we're allowed
- // to see, and we'll be exposing that stack to consumers of this promise.
- JSAutoCompartment ac(cx, GlobalJSObject());
- aRv.StealJSException(cx, &value);
- }
+ aRv.StealJSException(cx, &value);
// we want the same behavior as this JS implementation:
// function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }}
diff --git a/dom/promise/PromiseCallback.cpp b/dom/promise/PromiseCallback.cpp
index d578f8de44dd..81b8b4e423ad 100644
--- a/dom/promise/PromiseCallback.cpp
+++ b/dom/promise/PromiseCallback.cpp
@@ -214,15 +214,7 @@ WrapperPromiseCallback::Call(JSContext* aCx,
if (rv.Failed()) {
JS::Rooted value(aCx);
if (rv.IsJSException()) {
- { // scope for ac
- // Enter the compartment of mNextPromise before stealing the JS
- // exception, since the StealJSException call will use the current
- // compartment for a security check that determines how much of the
- // stack we're allowed to see and we'll be exposing that stack to
- // consumers of mPromise.
- JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject());
- rv.StealJSException(aCx, &value);
- }
+ rv.StealJSException(aCx, &value);
if (!JS_WrapValue(aCx, &value)) {
NS_WARNING("Failed to wrap value into the right compartment.");
diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp
index 72739ebf637f..8fd24867b8e3 100644
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -76,6 +76,7 @@ GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions,
{
size_t dashArrayLength;
Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0;
+ Float pathScale = 1.0;
if (aContextPaint && aStyleSVG->mStrokeDasharrayFromObject) {
const FallibleTArray& dashSrc = aContextPaint->GetStrokeDashArray();
@@ -100,7 +101,6 @@ GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions,
if (dashArrayLength <= 0) {
return eContinuousStroke;
}
- Float pathScale = 1.0;
if (aElement->IsSVGElement(nsGkAtoms::path)) {
pathScale = static_cast(aElement)->
GetPathLengthScale(SVGPathElement::eForStroking);
@@ -161,7 +161,8 @@ GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions,
aStrokeOptions->mDashOffset = Float(aContextPaint->GetStrokeDashOffset());
} else {
aStrokeOptions->mDashOffset =
- SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset);
+ SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset) *
+ pathScale;
}
return eDashedStroke;
diff --git a/dom/tests/mochitest/general/test_consoleAPI.html b/dom/tests/mochitest/general/test_consoleAPI.html
index 85038a3a94f3..8d710a51a421 100644
--- a/dom/tests/mochitest/general/test_consoleAPI.html
+++ b/dom/tests/mochitest/general/test_consoleAPI.html
@@ -38,7 +38,12 @@ function doTest() {
"assert": "function",
"count": "function",
"table": "function",
- "__noSuchMethod__": "function"
+ "clear": "function",
+ "dirxml": "function",
+ "markTimeline": "function",
+ "timeline": "function",
+ "timelineEnd": "function",
+ "timeStamp": "function",
};
var foundProps = 0;
diff --git a/dom/webidl/Cache.webidl b/dom/webidl/Cache.webidl
index 099701d2bdcd..7df69a8f6e0c 100644
--- a/dom/webidl/Cache.webidl
+++ b/dom/webidl/Cache.webidl
@@ -33,7 +33,6 @@ dictionary CacheQueryOptions {
boolean ignoreSearch = false;
boolean ignoreMethod = false;
boolean ignoreVary = false;
-boolean prefixMatch = false;
DOMString cacheName;
};
diff --git a/dom/webidl/Client.webidl b/dom/webidl/Client.webidl
index 570a9de527ac..c381f84f62d3 100644
--- a/dom/webidl/Client.webidl
+++ b/dom/webidl/Client.webidl
@@ -10,8 +10,23 @@
[Exposed=ServiceWorker]
interface Client {
- readonly attribute unsigned long id;
+ readonly attribute USVString url;
[Throws]
void postMessage(any message, optional sequence transfer);
};
+
+[Exposed=ServiceWorker]
+interface WindowClient : Client {
+ readonly attribute VisibilityState visibilityState;
+ readonly attribute boolean focused;
+ readonly attribute FrameType frameType;
+ Promise focus();
+};
+
+enum FrameType {
+ "auxiliary",
+ "top-level",
+ "nested",
+ "none"
+};
diff --git a/dom/webidl/Clients.webidl b/dom/webidl/Clients.webidl
index d3a75cf4e52f..1ae5dd67845b 100644
--- a/dom/webidl/Clients.webidl
+++ b/dom/webidl/Clients.webidl
@@ -10,10 +10,11 @@
[Exposed=ServiceWorker]
interface Clients {
- // A list of client objects, identifiable by ID, that correspond to windows
- // (or workers) that are "controlled" by this SW
+ // The objects returned will be new instances every time
[Throws]
Promise?> matchAll(optional ClientQueryOptions options);
+ Promise openWindow(USVString url);
+ Promise claim();
};
dictionary ClientQueryOptions {
diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl
index 9446fcffe62d..2eb10aa2392e 100644
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -28,7 +28,19 @@ interface Console {
void assert(boolean condition, any... data);
void count(any... data);
- void ___noSuchMethod__();
+ // No-op methods for compatibility with other browsers.
+ [BinaryName="noopMethod"]
+ void clear();
+ [BinaryName="noopMethod"]
+ void dirxml();
+ [BinaryName="noopMethod"]
+ void markTimeline();
+ [BinaryName="noopMethod"]
+ void timeline();
+ [BinaryName="noopMethod"]
+ void timelineEnd();
+ [BinaryName="noopMethod"]
+ void timeStamp();
};
// This is used to propagate console events to the observers.
diff --git a/dom/webidl/DragEvent.webidl b/dom/webidl/DragEvent.webidl
index 03c2e6f61445..913cb20727d9 100644
--- a/dom/webidl/DragEvent.webidl
+++ b/dom/webidl/DragEvent.webidl
@@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+[Constructor(DOMString type, optional DragEventInit eventInitDict)]
interface DragEvent : MouseEvent
{
readonly attribute DataTransfer? dataTransfer;
@@ -26,3 +27,8 @@ interface DragEvent : MouseEvent
EventTarget? aRelatedTarget,
DataTransfer? aDataTransfer);
};
+
+dictionary DragEventInit : MouseEventInit
+{
+ DataTransfer? dataTransfer = null;
+};
diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl
index 2c95d91314d6..c8218bd6ab58 100644
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -185,6 +185,11 @@ partial interface Element {
void scrollTo(optional ScrollToOptions options);
void scrollBy(unrestricted double x, unrestricted double y);
void scrollBy(optional ScrollToOptions options);
+ // mozScrollSnap is used by chrome to perform scroll snapping after the
+ // user performs actions that may affect scroll position
+ // mozScrollSnap is deprecated, to be replaced by a web accessible API, such
+ // as an extension to the ScrollOptions dictionary. See bug 1137937.
+ [ChromeOnly] void mozScrollSnap();
readonly attribute long clientTop;
readonly attribute long clientLeft;
diff --git a/dom/webidl/FetchEvent.webidl b/dom/webidl/FetchEvent.webidl
index b02bcd8daebc..5f53dfb5f5e8 100644
--- a/dom/webidl/FetchEvent.webidl
+++ b/dom/webidl/FetchEvent.webidl
@@ -12,7 +12,9 @@
Exposed=(ServiceWorker)]
interface FetchEvent : Event {
readonly attribute Request request;
- readonly attribute Client client; // The window issuing the request.
+
+ // https://github.com/slightlyoff/ServiceWorker/issues/631
+ readonly attribute Client? client; // The window issuing the request.
readonly attribute boolean isReload;
[Throws] void respondWith(Promise r);
diff --git a/dom/webidl/ServiceWorkerContainer.webidl b/dom/webidl/ServiceWorkerContainer.webidl
index c06f29eb3c48..18879912c5d5 100644
--- a/dom/webidl/ServiceWorkerContainer.webidl
+++ b/dom/webidl/ServiceWorkerContainer.webidl
@@ -37,14 +37,8 @@ interface ServiceWorkerContainer : EventTarget {
// Testing only.
partial interface ServiceWorkerContainer {
- [Throws,Pref="dom.serviceWorkers.testing.enabled"]
- Promise clearAllServiceWorkerData();
-
[Throws,Pref="dom.serviceWorkers.testing.enabled"]
DOMString getScopeForUrl(DOMString url);
-
- [Throws,Pref="dom.serviceWorkers.testing.enabled"]
- DOMString getControllingWorkerScriptURLForPath(DOMString path);
};
dictionary RegistrationOptionList {
diff --git a/dom/webidl/ServiceWorkerGlobalScope.webidl b/dom/webidl/ServiceWorkerGlobalScope.webidl
index 3690b04b50f7..0d816e993047 100644
--- a/dom/webidl/ServiceWorkerGlobalScope.webidl
+++ b/dom/webidl/ServiceWorkerGlobalScope.webidl
@@ -13,14 +13,8 @@
[Global=(Worker,ServiceWorker),
Exposed=ServiceWorker]
interface ServiceWorkerGlobalScope : WorkerGlobalScope {
- // FIXME(nsm): Bug 982725
- // readonly attribute CacheList caches;
-
readonly attribute Clients clients;
- // FIXME(nsm): Bug 995484
- // ResponsePromise fetch((Request or [EnsureUTF16] DOMString) request);
-
void update();
[Throws]
@@ -34,10 +28,6 @@ interface ServiceWorkerGlobalScope : WorkerGlobalScope {
// The event.source of these MessageEvents are instances of Client
attribute EventHandler onmessage;
-
- // close() method inherited from WorkerGlobalScope is not exposed.
- // FIXME(nsm): For now, overridden so it can be a no-op.
- void close();
};
diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl
index a32af1ee69fd..415357e51cf0 100644
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -190,6 +190,11 @@ partial interface Window {
void scrollTo(optional ScrollToOptions options);
void scrollBy(unrestricted double x, unrestricted double y);
void scrollBy(optional ScrollToOptions options);
+ // mozScrollSnap is used by chrome to perform scroll snapping after the
+ // user performs actions that may affect scroll position
+ // mozScrollSnap is deprecated, to be replaced by a web accessible API, such
+ // as an extension to the ScrollOptions dictionary. See bug 1137937.
+ [ChromeOnly] void mozScrollSnap();
[Replaceable, Throws] readonly attribute long scrollX;
[Throws] readonly attribute long pageXOffset;
[Replaceable, Throws] readonly attribute long scrollY;
diff --git a/dom/webidl/WorkerGlobalScope.webidl b/dom/webidl/WorkerGlobalScope.webidl
index 2af3d661b6a3..10487b667e10 100644
--- a/dom/webidl/WorkerGlobalScope.webidl
+++ b/dom/webidl/WorkerGlobalScope.webidl
@@ -21,6 +21,7 @@ interface WorkerGlobalScope : EventTarget {
readonly attribute WorkerLocation location;
+ [Throws]
void close();
attribute OnErrorEventHandler onerror;
diff --git a/dom/workers/ServiceWorkerClient.cpp b/dom/workers/ServiceWorkerClient.cpp
index 282b2e19242f..649d4a80aca2 100644
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -9,10 +9,9 @@
#include "mozilla/dom/MessageEvent.h"
#include "nsGlobalWindow.h"
+#include "nsIDocument.h"
#include "WorkerPrivate.h"
-#include "mozilla/dom/ClientBinding.h"
-
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::workers;
@@ -27,6 +26,31 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
+ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
+{
+ MOZ_ASSERT(aDoc);
+ MOZ_ASSERT(aDoc->GetWindow());
+
+ nsRefPtr outerWindow = static_cast(aDoc->GetWindow());
+ mClientId = outerWindow->WindowID();
+ aDoc->GetURL(mUrl);
+ mVisibilityState = aDoc->VisibilityState();
+
+ ErrorResult result;
+ mFocused = aDoc->HasFocus(result);
+ if (result.Failed()) {
+ NS_WARNING("Failed to get focus information.");
+ }
+
+ if (!outerWindow->IsTopLevelWindow()) {
+ mFrameType = FrameType::Nested;
+ } else if (outerWindow->HadOriginalOpener()) {
+ mFrameType = FrameType::Auxiliary;
+ } else {
+ mFrameType = FrameType::Top_level;
+ }
+}
+
JSObject*
ServiceWorkerClient::WrapObject(JSContext* aCx)
{
@@ -55,7 +79,7 @@ public:
Run()
{
AssertIsOnMainThread();
- nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mId);
+ nsGlobalWindow* window = nsGlobalWindow::GetOuterWindowWithId(mId);
if (!window) {
return NS_ERROR_FAILURE;
}
diff --git a/dom/workers/ServiceWorkerClient.h b/dom/workers/ServiceWorkerClient.h
index e2d63b94166b..3b9be0a9252a 100644
--- a/dom/workers/ServiceWorkerClient.h
+++ b/dom/workers/ServiceWorkerClient.h
@@ -11,47 +11,78 @@
#include "nsWrapperCache.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ClientBinding.h"
namespace mozilla {
namespace dom {
namespace workers {
-class ServiceWorkerClient MOZ_FINAL : public nsISupports,
- public nsWrapperCache
+class ServiceWorkerClient;
+class ServiceWorkerWindowClient;
+
+// Used as a container object for information needed to create
+// client objects.
+class ServiceWorkerClientInfo MOZ_FINAL
+{
+ friend class ServiceWorkerClient;
+ friend class ServiceWorkerWindowClient;
+
+public:
+ explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
+
+private:
+ uint64_t mClientId;
+ nsString mUrl;
+
+ // Window Clients
+ VisibilityState mVisibilityState;
+ bool mFocused;
+ FrameType mFrameType;
+};
+
+class ServiceWorkerClient : public nsISupports,
+ public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
- ServiceWorkerClient(nsISupports* aOwner, uint64_t aId)
+ ServiceWorkerClient(nsISupports* aOwner,
+ const ServiceWorkerClientInfo& aClientInfo)
: mOwner(aOwner),
- mId(aId)
+ mId(aClientInfo.mClientId),
+ mUrl(aClientInfo.mUrl)
{
+ MOZ_ASSERT(aOwner);
}
- uint32_t Id() const
- {
- return mId;
- }
-
- nsISupports* GetParentObject() const
+ nsISupports*
+ GetParentObject() const
{
return mOwner;
}
- void PostMessage(JSContext* aCx, JS::Handle aMessage,
- const Optional>& aTransferable,
- ErrorResult& aRv);
+ void
+ GetUrl(nsAString& aUrl) const
+ {
+ aUrl.Assign(mUrl);
+ }
+
+ void
+ PostMessage(JSContext* aCx, JS::Handle aMessage,
+ const Optional>& aTransferable,
+ ErrorResult& aRv);
JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
-private:
- ~ServiceWorkerClient()
- {
- }
+protected:
+ virtual ~ServiceWorkerClient()
+ { }
+private:
nsCOMPtr mOwner;
uint64_t mId;
+ nsString mUrl;
};
} // namespace workers
diff --git a/dom/workers/ServiceWorkerClients.cpp b/dom/workers/ServiceWorkerClients.cpp
index 8852442e5e84..0da245be732a 100644
--- a/dom/workers/ServiceWorkerClients.cpp
+++ b/dom/workers/ServiceWorkerClients.cpp
@@ -8,6 +8,7 @@
#include "ServiceWorkerClient.h"
#include "ServiceWorkerClients.h"
#include "ServiceWorkerManager.h"
+#include "ServiceWorkerWindowClient.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
@@ -122,17 +123,17 @@ private:
class ResolvePromiseWorkerRunnable MOZ_FINAL : public WorkerRunnable
{
nsRefPtr mPromiseHolder;
- nsAutoPtr> mValue;
+ nsTArray mValue;
public:
ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
PromiseHolder* aPromiseHolder,
- nsAutoPtr>& aValue)
+ nsTArray& aValue)
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
- mPromiseHolder(aPromiseHolder),
- mValue(aValue)
+ mPromiseHolder(aPromiseHolder)
{
AssertIsOnMainThread();
+ mValue.SwapElements(aValue);
}
bool
@@ -145,10 +146,10 @@ public:
MOZ_ASSERT(promise);
nsTArray> ret;
- for (size_t i = 0; i < mValue->Length(); i++) {
+ for (size_t i = 0; i < mValue.Length(); i++) {
ret.AppendElement(nsRefPtr(
- new ServiceWorkerClient(promise->GetParentObject(),
- mValue->ElementAt(i))));
+ new ServiceWorkerWindowClient(promise->GetParentObject(),
+ mValue.ElementAt(i))));
}
promise->MaybeResolve(ret);
@@ -216,7 +217,7 @@ public:
}
nsRefPtr swm = ServiceWorkerManager::GetInstance();
- nsAutoPtr> result(new nsTArray());
+ nsTArray result;
swm->GetAllClients(mScope, result);
nsRefPtr r =
@@ -282,3 +283,29 @@ ServiceWorkerClients::MatchAll(const ClientQueryOptions& aOptions,
return promise.forget();
}
+
+already_AddRefed
+ServiceWorkerClients::OpenWindow(const nsAString& aUrl)
+{
+ ErrorResult result;
+ nsRefPtr promise = Promise::Create(mWorkerScope, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return nullptr;
+ }
+
+ promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+ return promise.forget();
+}
+
+already_AddRefed
+ServiceWorkerClients::Claim()
+{
+ ErrorResult result;
+ nsRefPtr promise = Promise::Create(mWorkerScope, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return nullptr;
+ }
+
+ promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+ return promise.forget();
+}
diff --git a/dom/workers/ServiceWorkerClients.h b/dom/workers/ServiceWorkerClients.h
index cd3dcb5f6def..aa260c2677d3 100644
--- a/dom/workers/ServiceWorkerClients.h
+++ b/dom/workers/ServiceWorkerClients.h
@@ -31,6 +31,12 @@ public:
already_AddRefed
MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv);
+ already_AddRefed
+ OpenWindow(const nsAString& aUrl);
+
+ already_AddRefed
+ Claim();
+
JSObject*
WrapObject(JSContext* aCx) MOZ_OVERRIDE;
diff --git a/dom/workers/ServiceWorkerContainer.cpp b/dom/workers/ServiceWorkerContainer.cpp
index 9d37400ef169..5e44a7e6a24e 100644
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -181,14 +181,6 @@ ServiceWorkerContainer::GetReady(ErrorResult& aRv)
return mReadyPromise;
}
-// Testing only.
-already_AddRefed
-ServiceWorkerContainer::ClearAllServiceWorkerData(ErrorResult& aRv)
-{
- aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
- return nullptr;
-}
-
// Testing only.
void
ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
@@ -204,14 +196,5 @@ ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
aRv = swm->GetScopeForUrl(aUrl, aScope);
}
-// Testing only.
-void
-ServiceWorkerContainer::GetControllingWorkerScriptURLForPath(
- const nsAString& aPath,
- nsString& aScriptURL,
- ErrorResult& aRv)
-{
- aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-}
} // namespace dom
} // namespace mozilla
diff --git a/dom/workers/ServiceWorkerContainer.h b/dom/workers/ServiceWorkerContainer.h
index 773798c38d22..d3f6d08f276b 100644
--- a/dom/workers/ServiceWorkerContainer.h
+++ b/dom/workers/ServiceWorkerContainer.h
@@ -56,20 +56,10 @@ public:
Promise*
GetReady(ErrorResult& aRv);
- // Testing only.
- already_AddRefed
- ClearAllServiceWorkerData(ErrorResult& aRv);
-
// Testing only.
void
GetScopeForUrl(const nsAString& aUrl, nsString& aScope, ErrorResult& aRv);
- // Testing only.
- void
- GetControllingWorkerScriptURLForPath(const nsAString& aPath,
- nsString& aScriptURL,
- ErrorResult& aRv);
-
// DOMEventTargetHelper
void DisconnectFromOwner() MOZ_OVERRIDE;
diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp
index 6199f3b5f405..0b1e46cfdaeb 100644
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -28,7 +28,6 @@ BEGIN_WORKERS_NAMESPACE
FetchEvent::FetchEvent(EventTarget* aOwner)
: Event(aOwner, nullptr, nullptr)
-, mWindowId(0)
, mIsReload(false)
, mWaitToRespond(false)
{
@@ -41,11 +40,11 @@ FetchEvent::~FetchEvent()
void
FetchEvent::PostInit(nsMainThreadPtrHandle& aChannel,
nsMainThreadPtrHandle& aServiceWorker,
- uint64_t aWindowId)
+ nsAutoPtr& aClientInfo)
{
mChannel = aChannel;
mServiceWorker = aServiceWorker;
- mWindowId = aWindowId;
+ mClientInfo = aClientInfo;
}
/*static*/ already_AddRefed
@@ -244,10 +243,14 @@ FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv)
}
already_AddRefed
-FetchEvent::Client()
+FetchEvent::GetClient()
{
if (!mClient) {
- mClient = new ServiceWorkerClient(GetParentObject(), mWindowId);
+ if (!mClientInfo) {
+ return nullptr;
+ }
+
+ mClient = new ServiceWorkerClient(GetParentObject(), *mClientInfo);
}
nsRefPtr client = mClient;
return client.forget();
diff --git a/dom/workers/ServiceWorkerEvents.h b/dom/workers/ServiceWorkerEvents.h
index 7a20cc87995c..907838398ef0 100644
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -32,7 +32,7 @@ class FetchEvent MOZ_FINAL : public Event
nsMainThreadPtrHandle mServiceWorker;
nsRefPtr mClient;
nsRefPtr mRequest;
- uint64_t mWindowId;
+ nsAutoPtr mClientInfo;
bool mIsReload;
bool mWaitToRespond;
protected:
@@ -51,7 +51,7 @@ public:
void PostInit(nsMainThreadPtrHandle& aChannel,
nsMainThreadPtrHandle& aServiceWorker,
- uint64_t aWindowId);
+ nsAutoPtr& aClientInfo);
static already_AddRefed
Constructor(const GlobalObject& aGlobal,
@@ -72,7 +72,7 @@ public:
}
already_AddRefed
- Client();
+ GetClient();
bool
IsReload() const
diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
index eec761821c13..8442e48dc57a 100644
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2088,7 +2088,7 @@ class FetchEventRunnable : public WorkerRunnable
nsMainThreadPtrHandle mServiceWorker;
nsTArray mHeaderNames;
nsTArray mHeaderValues;
- uint64_t mWindowId;
+ nsAutoPtr mClientInfo;
nsCString mSpec;
nsCString mMethod;
bool mIsReload;
@@ -2096,11 +2096,11 @@ public:
FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
nsMainThreadPtrHandle& aChannel,
nsMainThreadPtrHandle& aServiceWorker,
- uint64_t aWindowId)
+ nsAutoPtr& aClientInfo)
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
, mInterceptedChannel(aChannel)
, mServiceWorker(aServiceWorker)
- , mWindowId(aWindowId)
+ , mClientInfo(aClientInfo)
{
MOZ_ASSERT(aWorkerPrivate);
}
@@ -2225,7 +2225,7 @@ private:
return false;
}
- event->PostInit(mInterceptedChannel, mServiceWorker, mWindowId);
+ event->PostInit(mInterceptedChannel, mServiceWorker, mClientInfo);
event->SetTrusted(true);
nsRefPtr target = do_QueryObject(aWorkerPrivate->GlobalScope());
@@ -2250,9 +2250,12 @@ ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChanne
nsresult rv = aChannel->GetIsNavigation(&isNavigation);
NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoPtr clientInfo;
+
if (!isNavigation) {
MOZ_ASSERT(aDoc);
rv = GetDocumentController(aDoc->GetInnerWindow(), getter_AddRefs(serviceWorker));
+ clientInfo = new ServiceWorkerClientInfo(aDoc);
} else {
nsCOMPtr internalChannel;
rv = aChannel->GetChannel(getter_AddRefs(internalChannel));
@@ -2282,14 +2285,13 @@ ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChanne
nsMainThreadPtrHandle handle(
new nsMainThreadPtrHolder(aChannel, false));
- uint64_t windowId = aDoc ? aDoc->GetInnerWindow()->WindowID() : 0;
-
nsRefPtr sw = static_cast(serviceWorker.get());
nsMainThreadPtrHandle serviceWorkerHandle(
new nsMainThreadPtrHolder(sw));
+ // clientInfo is null if we don't have a controlled document
nsRefPtr event =
- new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, windowId);
+ new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, clientInfo);
rv = event->Init();
NS_ENSURE_SUCCESS(rv, rv);
@@ -2519,14 +2521,14 @@ namespace {
class MOZ_STACK_CLASS FilterRegistrationData
{
public:
- FilterRegistrationData(nsTArray* aDocuments,
- ServiceWorkerRegistrationInfo* aRegistration)
+ FilterRegistrationData(nsTArray& aDocuments,
+ ServiceWorkerRegistrationInfo* aRegistration)
: mDocuments(aDocuments),
mRegistration(aRegistration)
{
}
- nsTArray* mDocuments;
+ nsTArray& mDocuments;
nsRefPtr mRegistration;
};
@@ -2539,12 +2541,16 @@ EnumControlledDocuments(nsISupports* aKey,
if (data->mRegistration != aRegistration) {
return PL_DHASH_NEXT;
}
+
nsCOMPtr document = do_QueryInterface(aKey);
- if (!document || !document->GetInnerWindow()) {
+
+ if (!document || !document->GetWindow()) {
return PL_DHASH_NEXT;
}
- data->mDocuments->AppendElement(document->GetInnerWindow()->WindowID());
+ ServiceWorkerClientInfo clientInfo(document);
+ data->mDocuments.AppendElement(clientInfo);
+
return PL_DHASH_NEXT;
}
@@ -2591,7 +2597,7 @@ FireControllerChangeOnMatchingDocument(nsISupports* aKey,
void
ServiceWorkerManager::GetAllClients(const nsCString& aScope,
- nsTArray* aControlledDocuments)
+ nsTArray& aControlledDocuments)
{
nsRefPtr registration = GetRegistration(aScope);
diff --git a/dom/workers/ServiceWorkerManager.h b/dom/workers/ServiceWorkerManager.h
index 0ee4f3bc5239..0352750c57cc 100644
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -43,6 +43,7 @@ class ServiceWorkerRegistration;
namespace workers {
class ServiceWorker;
+class ServiceWorkerClientInfo;
class ServiceWorkerInfo;
class ServiceWorkerJobQueue;
@@ -379,7 +380,7 @@ public:
void
GetAllClients(const nsCString& aScope,
- nsTArray* aControlledDocuments);
+ nsTArray& aControlledDocuments);
static already_AddRefed
GetInstance();
diff --git a/dom/workers/ServiceWorkerWindowClient.cpp b/dom/workers/ServiceWorkerWindowClient.cpp
new file mode 100644
index 000000000000..d6cddf21f5db
--- /dev/null
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "ServiceWorkerWindowClient.h"
+
+#include "mozilla/dom/ClientBinding.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+JSObject*
+ServiceWorkerWindowClient::WrapObject(JSContext* aCx)
+{
+ return WindowClientBinding::Wrap(aCx, this);
+}
+
+already_AddRefed
+ServiceWorkerWindowClient::Focus() const
+{
+ ErrorResult result;
+ nsCOMPtr global = do_QueryInterface(GetParentObject());
+ MOZ_ASSERT(global);
+
+ nsRefPtr promise = Promise::Create(global, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return nullptr;
+ }
+
+ promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+ return promise.forget();
+}
diff --git a/dom/workers/ServiceWorkerWindowClient.h b/dom/workers/ServiceWorkerWindowClient.h
new file mode 100644
index 000000000000..8ea64e11cb3b
--- /dev/null
+++ b/dom/workers/ServiceWorkerWindowClient.h
@@ -0,0 +1,65 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef mozilla_dom_workers_serviceworkerwindowclient_h
+#define mozilla_dom_workers_serviceworkerwindowclient_h
+
+#include "ServiceWorkerClient.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerWindowClient MOZ_FINAL : public ServiceWorkerClient
+{
+public:
+ ServiceWorkerWindowClient(nsISupports* aOwner,
+ const ServiceWorkerClientInfo& aClientInfo)
+ : ServiceWorkerClient(aOwner, aClientInfo),
+ mVisibilityState(aClientInfo.mVisibilityState),
+ mFocused(aClientInfo.mFocused),
+ mFrameType(aClientInfo.mFrameType)
+ {
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+ mozilla::dom::VisibilityState
+ VisibilityState() const
+ {
+ return mVisibilityState;
+ }
+
+ bool
+ Focused() const
+ {
+ return mFocused;
+ }
+
+ mozilla::dom::FrameType
+ FrameType() const
+ {
+ return mFrameType;
+ }
+
+ already_AddRefed
+ Focus() const;
+
+private:
+ ~ServiceWorkerWindowClient()
+ { }
+
+ mozilla::dom::VisibilityState mVisibilityState;
+ bool mFocused;
+ mozilla::dom::FrameType mFrameType;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerwindowclient_h
diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp
index 9ff43d9ef2db..79d01d29aa61 100644
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -171,11 +171,15 @@ WorkerGlobalScope::GetExistingNavigator() const
}
void
-WorkerGlobalScope::Close(JSContext* aCx)
+WorkerGlobalScope::Close(JSContext* aCx, ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
- mWorkerPrivate->CloseInternal(aCx);
+ if (mWorkerPrivate->IsServiceWorker()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ } else {
+ mWorkerPrivate->CloseInternal(aCx);
+ }
}
OnErrorEventHandlerNonNull*
diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h
index a9eff448b707..4ffe0a88b0b4 100644
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -98,7 +98,7 @@ public:
GetExistingNavigator() const;
void
- Close(JSContext* aCx);
+ Close(JSContext* aCx, ErrorResult& aRv);
OnErrorEventHandlerNonNull*
GetOnerror();
@@ -218,12 +218,6 @@ public:
aScope = mScope;
}
- void
- Close() const
- {
- // no-op close.
- }
-
void
Update();
diff --git a/dom/workers/moz.build b/dom/workers/moz.build
index 2b9a16bceb99..dbd5a220dec0 100644
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -34,6 +34,7 @@ EXPORTS.mozilla.dom.workers.bindings += [
'ServiceWorker.h',
'ServiceWorkerClient.h',
'ServiceWorkerClients.h',
+ 'ServiceWorkerWindowClient.h',
'SharedWorker.h',
'URL.h',
'WorkerFeature.h',
@@ -69,6 +70,7 @@ UNIFIED_SOURCES += [
'ServiceWorkerManager.cpp',
'ServiceWorkerRegistrar.cpp',
'ServiceWorkerRegistration.cpp',
+ 'ServiceWorkerWindowClient.cpp',
'SharedWorker.cpp',
'URL.cpp',
'WorkerDebuggerManager.cpp',
diff --git a/dom/workers/test/console_worker.js b/dom/workers/test/console_worker.js
index 4e0d68defdc5..7f4b6a5cdfb6 100644
--- a/dom/workers/test/console_worker.js
+++ b/dom/workers/test/console_worker.js
@@ -11,10 +11,6 @@ onmessage = function(event) {
postMessage({event: 'trace without function', status: true, last : false});
- for (var i = 0; i < 10; ++i) {
- console.what('1', 123, 321);
- }
-
for (var i = 0; i < 10; ++i) {
console.log(i, i, i);
}
@@ -81,7 +77,6 @@ function nextSteps(event) {
namelessTimer();
var str = "Test Message."
- console.foobar(str); // if this throws, we don't execute following funcs
console.log(str);
console.info(str);
console.warn(str);
@@ -91,6 +86,8 @@ function nextSteps(event) {
console.assert(false, str);
console.profile(str);
console.profileEnd(str);
+ console.timeStamp();
+ console.clear();
postMessage({event: '4 messages', status: true, last : false});
// Recursive:
diff --git a/dom/workers/test/serviceworkers/close_test.js b/dom/workers/test/serviceworkers/close_test.js
new file mode 100644
index 000000000000..6138d6421e6d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/close_test.js
@@ -0,0 +1,19 @@
+function ok(v, msg) {
+ client.postMessage({status: "ok", result: !!v, message: msg});
+}
+
+var client;
+onmessage = function(e) {
+ if (e.data.message == "start") {
+ self.clients.matchAll().then(function(clients) {
+ client = clients[0];
+ try {
+ close();
+ ok(false, "close() should throw");
+ } catch (e) {
+ ok(e.name === "InvalidAccessError", "close() should throw InvalidAccessError");
+ }
+ client.postMessage({status: "done"});
+ });
+ }
+}
diff --git a/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
new file mode 100644
index 000000000000..1b7e2bf3ee8c
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
@@ -0,0 +1,61 @@
+
+
+
+
+ Bug 1058311 - controlled page
+
+
+
+
+
+
diff --git a/dom/workers/test/serviceworkers/match_all_properties_worker.js b/dom/workers/test/serviceworkers/match_all_properties_worker.js
new file mode 100644
index 000000000000..f42eecab01db
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_properties_worker.js
@@ -0,0 +1,19 @@
+onmessage = function(e) {
+ dump("MatchAllPropertiesWorker:" + e.data + "\n");
+ self.clients.matchAll().then(function(res) {
+ if (!res.length) {
+ dump("ERROR: no clients are currently controlled.\n");
+ }
+
+ for (i = 0; i < res.length; i++) {
+ client = res[i];
+ response = {
+ url: client.url,
+ visibilityState: client.visibilityState,
+ focused: client.focused,
+ frameType: client.frameType
+ };
+ client.postMessage(response);
+ }
+ });
+}
diff --git a/dom/workers/test/serviceworkers/message_receiver.html b/dom/workers/test/serviceworkers/message_receiver.html
new file mode 100644
index 000000000000..82cb587c7275
--- /dev/null
+++ b/dom/workers/test/serviceworkers/message_receiver.html
@@ -0,0 +1,6 @@
+
+
diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini
index 00920aaa443d..6b74b649783b 100644
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -24,6 +24,12 @@ support-files =
fetch/index.html
fetch/fetch_worker_script.js
fetch/fetch_tests.js
+ match_all_properties_worker.js
+ match_all_clients/match_all_controlled.html
+ test_serviceworker_interfaces.js
+ serviceworker_wrapper.js
+ message_receiver.html
+ close_test.js
[test_unregister.html]
skip-if = true # Bug 1133805
@@ -33,6 +39,7 @@ skip-if = true # Bug 1133805
[test_install_event.html]
[test_navigator.html]
[test_scopes.html]
+skip-if = true # Bug 1037739
[test_controller.html]
[test_workerUpdate.html]
skip-if = true # Bug 1133805
@@ -40,3 +47,6 @@ skip-if = true # Bug 1133805
skip-if = true # Bug 1133805
[test_post_message.html]
[test_post_message_advanced.html]
+[test_match_all_client_properties.html]
+[test_close.html]
+[test_serviceworker_interfaces.html]
diff --git a/dom/workers/test/serviceworkers/serviceworker_wrapper.js b/dom/workers/test/serviceworkers/serviceworker_wrapper.js
new file mode 100644
index 000000000000..318450948934
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworker_wrapper.js
@@ -0,0 +1,131 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// ServiceWorker equivalent of worker_wrapper.js.
+
+var client;
+
+function ok(a, msg) {
+ dump("OK: " + !!a + " => " + a + ": " + msg + "\n");
+ client.postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+ dump("IS: " + (a===b) + " => " + a + " | " + b + ": " + msg + "\n");
+ client.postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function workerTestArrayEquals(a, b) {
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length != b.length) {
+ return false;
+ }
+ for (var i = 0, n = a.length; i < n; ++i) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function workerTestDone() {
+ client.postMessage({ type: 'finish' });
+}
+
+function workerTestGetPrefs(prefs, cb) {
+ addEventListener('message', function workerTestGetPrefsCB(e) {
+ if (e.data.type != 'returnPrefs' ||
+ !workerTestArrayEquals(prefs, e.data.prefs)) {
+ return;
+ }
+ removeEventListener('message', workerTestGetPrefsCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getPrefs',
+ prefs: prefs
+ });
+}
+
+function workerTestGetPermissions(permissions, cb) {
+ addEventListener('message', function workerTestGetPermissionsCB(e) {
+ if (e.data.type != 'returnPermissions' ||
+ !workerTestArrayEquals(permissions, e.data.permissions)) {
+ return;
+ }
+ removeEventListener('message', workerTestGetPermissionsCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getPermissions',
+ permissions: permissions
+ });
+}
+
+function workerTestGetVersion(cb) {
+ addEventListener('message', function workerTestGetVersionCB(e) {
+ if (e.data.type !== 'returnVersion') {
+ return;
+ }
+ removeEventListener('message', workerTestGetVersionCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getVersion'
+ });
+}
+
+function workerTestGetUserAgent(cb) {
+ addEventListener('message', function workerTestGetUserAgentCB(e) {
+ if (e.data.type !== 'returnUserAgent') {
+ return;
+ }
+ removeEventListener('message', workerTestGetUserAgentCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getUserAgent'
+ });
+}
+
+function workerTestGetOSCPU(cb) {
+ addEventListener('message', function workerTestGetOSCPUCB(e) {
+ if (e.data.type !== 'returnOSCPU') {
+ return;
+ }
+ removeEventListener('message', workerTestGetOSCPUCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getOSCPU'
+ });
+}
+
+function workerTestGetIsB2G(cb) {
+ addEventListener('message', function workerTestGetIsB2GCB(e) {
+ if (e.data.type !== 'returnIsB2G') {
+ return;
+ }
+ removeEventListener('message', workerTestGetIsB2GCB);
+ cb(e.data.result);
+ });
+ client.postMessage({
+ type: 'getIsB2G'
+ });
+}
+
+addEventListener('message', function workerWrapperOnMessage(e) {
+ removeEventListener('message', workerWrapperOnMessage);
+ var data = e.data;
+ self.clients.matchAll().then(function(clients) {
+ client = clients[0];
+ try {
+ importScripts(data.script);
+ } catch(e) {
+ client.postMessage({
+ type: 'status',
+ status: false,
+ msg: 'worker failed to import ' + data.script + "; error: " + e.message
+ });
+ }
+ });
+});
diff --git a/dom/workers/test/serviceworkers/test_close.html b/dom/workers/test/serviceworkers/test_close.html
new file mode 100644
index 000000000000..04c9402d784d
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_close.html
@@ -0,0 +1,69 @@
+
+
+
+
+ Bug 1131353 - test WorkerGlobalScope.close() on service workers
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_match_all_client_properties.html b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
new file mode 100644
index 000000000000..394566ce5b41
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
@@ -0,0 +1,93 @@
+
+
+
+
+ Bug 1058311 - Test matchAll clients properties
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
new file mode 100644
index 000000000000..27d053601eb2
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
@@ -0,0 +1,113 @@
+
+
+
+
+ Validate Interfaces Exposed to Service Workers
+
+
+
+
+
+
+
+
diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
new file mode 100644
index 000000000000..818daf6eb79f
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -0,0 +1,280 @@
+// This is a list of all interfaces that are exposed to workers.
+// Please only add things to this list with great care and proper review
+// from the associated module peers.
+
+// This file lists global interfaces we want exposed and verifies they
+// are what we intend. Each entry in the arrays below can either be a
+// simple string with the interface name, or an object with a 'name'
+// property giving the interface name as a string, and additional
+// properties which qualify the exposure of that interface. For example:
+//
+// [
+// "AGlobalInterface",
+// {name: "ExperimentalThing", release: false},
+// {name: "OptionalThing", pref: "some.thing.enabled"},
+// ];
+//
+// See createInterfaceMap() below for a complete list of properties.
+
+// IMPORTANT: Do not change this list without review from
+// a JavaScript Engine peer!
+var ecmaGlobals =
+ [
+ "Array",
+ "ArrayBuffer",
+ "Boolean",
+ "DataView",
+ "Date",
+ "Error",
+ "EvalError",
+ "Float32Array",
+ "Float64Array",
+ "Function",
+ "Infinity",
+ "Int16Array",
+ "Int32Array",
+ "Int8Array",
+ "InternalError",
+ {name: "Intl", b2g: false, android: false},
+ "Iterator",
+ "JSON",
+ "Map",
+ "Math",
+ "NaN",
+ "Number",
+ "Object",
+ "Proxy",
+ "RangeError",
+ "ReferenceError",
+ "RegExp",
+ "Set",
+ {name: "SharedArrayBuffer", nightly: true},
+ {name: "SharedInt8Array", nightly: true},
+ {name: "SharedUint8Array", nightly: true},
+ {name: "SharedUint8ClampedArray", nightly: true},
+ {name: "SharedInt16Array", nightly: true},
+ {name: "SharedUint16Array", nightly: true},
+ {name: "SharedInt32Array", nightly: true},
+ {name: "SharedUint32Array", nightly: true},
+ {name: "SharedFloat32Array", nightly: true},
+ {name: "SharedFloat64Array", nightly: true},
+ {name: "SIMD", nightly: true},
+ {name: "Atomics", nightly: true},
+ "StopIteration",
+ "String",
+ "Symbol",
+ "SyntaxError",
+ {name: "TypedObject", nightly: true},
+ "TypeError",
+ "Uint16Array",
+ "Uint32Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "URIError",
+ "WeakMap",
+ "WeakSet",
+ ];
+// IMPORTANT: Do not change the list above without review from
+// a JavaScript Engine peer!
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+var interfaceNamesInGlobalScope =
+ [
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Blob",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "BroadcastChannel", pref: "dom.broadcastChannel.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Cache", pref: "dom.caches.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "CacheStorage", pref: "dom.caches.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Client",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Clients",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DataStore", b2g: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "DataStoreCursor", b2g: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMError",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMException",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "DOMStringList",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Event",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "EventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ExtendableEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FetchEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "File",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReaderSync",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "Headers", pref: "dom.fetch.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBDatabase",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBFactory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBIndex",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBKeyRange",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBObjectStore",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBOpenDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBTransaction",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "IDBVersionChangeEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ImageData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "InstallEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessageEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "MessagePort",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Performance",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Promise",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Request",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "Response",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ServiceWorker",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "ServiceWorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextDecoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "TextEncoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "XMLHttpRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "XMLHttpRequestEventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "XMLHttpRequestUpload",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "URL",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "URLSearchParams",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ { name: "WebSocket", pref: "dom.workers.websocket.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WindowClient",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerLocation",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ "WorkerNavigator",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+ ];
+// IMPORTANT: Do not change the list above without review from a DOM peer!
+
+function createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G) {
+ var isNightly = version.endsWith("a1");
+ var isRelease = !version.contains("a");
+ var isDesktop = !/Mobile|Tablet/.test(userAgent);
+ var isAndroid = !!navigator.userAgent.contains("Android");
+
+ var interfaceMap = {};
+
+ function addInterfaces(interfaces)
+ {
+ for (var entry of interfaces) {
+ if (typeof(entry) === "string") {
+ interfaceMap[entry] = true;
+ } else if ((entry.nightly === !isNightly) ||
+ (entry.desktop === !isDesktop) ||
+ (entry.android === !isAndroid) ||
+ (entry.b2g === !isB2G) ||
+ (entry.release === !isRelease) ||
+ (entry.pref && !prefMap[entry.pref]) ||
+ (entry.permission && !permissionMap[entry.permission])) {
+ interfaceMap[entry.name] = false;
+ } else {
+ interfaceMap[entry.name] = true;
+ }
+ }
+ }
+
+ addInterfaces(ecmaGlobals);
+ addInterfaces(interfaceNamesInGlobalScope);
+
+ return interfaceMap;
+}
+
+function runTest(prefMap, permissionMap, version, userAgent, isB2G) {
+ var interfaceMap = createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G);
+ for (var name of Object.getOwnPropertyNames(self)) {
+ // An interface name should start with an upper case character.
+ if (!/^[A-Z]/.test(name)) {
+ continue;
+ }
+ ok(interfaceMap[name],
+ "If this is failing: DANGER, are you sure you want to expose the new interface " + name +
+ " to all webpages as a property on the service worker? Do not make a change to this file without a " +
+ " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)");
+ delete interfaceMap[name];
+ }
+ for (var name of Object.keys(interfaceMap)) {
+ ok(name in self === interfaceMap[name],
+ name + " should " + (interfaceMap[name] ? "" : " NOT") + " be defined on the global scope");
+ if (!interfaceMap[name]) {
+ delete interfaceMap[name];
+ }
+ }
+ is(Object.keys(interfaceMap).length, 0,
+ "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
+}
+
+function appendPrefs(prefs, interfaces) {
+ for (var entry of interfaces) {
+ if (entry.pref !== undefined && prefs.indexOf(entry.pref) === -1) {
+ prefs.push(entry.pref);
+ }
+ }
+}
+
+var prefs = [];
+appendPrefs(prefs, ecmaGlobals);
+appendPrefs(prefs, interfaceNamesInGlobalScope);
+
+function appendPermissions(permissions, interfaces) {
+ for (var entry of interfaces) {
+ if (entry.permission !== undefined &&
+ permissions.indexOf(entry.permission) === -1) {
+ permissions.push(entry.permission);
+ }
+ }
+}
+
+var permissions = [];
+appendPermissions(permissions, ecmaGlobals);
+appendPermissions(permissions, interfaceNamesInGlobalScope);
+
+workerTestGetPrefs(prefs, function(prefMap) {
+ workerTestGetPermissions(permissions, function(permissionMap) {
+ workerTestGetVersion(function(version) {
+ workerTestGetUserAgent(function(userAgent) {
+ workerTestGetIsB2G(function(isB2G) {
+ runTest(prefMap, permissionMap, version, userAgent, isB2G);
+ workerTestDone();
+ });
+ });
+ });
+ });
+});
diff --git a/embedding/components/build/nsEmbeddingModule.cpp b/embedding/components/build/nsEmbeddingModule.cpp
index e766b10ee1ff..9ab26fbd75f5 100644
--- a/embedding/components/build/nsEmbeddingModule.cpp
+++ b/embedding/components/build/nsEmbeddingModule.cpp
@@ -20,7 +20,7 @@
#ifdef NS_PRINTING
#include "nsPrintingPromptService.h"
-#include "nsPrintingPromptServiceProxy.h"
+#include "nsPrintingProxy.h"
#endif
@@ -40,7 +40,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDialogParamBlock)
#ifdef NS_PRINTING
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintingPromptService, Init)
#ifdef PROXY_PRINTING
-NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintingPromptServiceProxy, Init)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPrintingProxy,
+ nsPrintingProxy::GetInstance)
#endif
#endif
#endif
@@ -70,7 +71,7 @@ static const mozilla::Module::CIDEntry kEmbeddingCIDs[] = {
#ifdef PROXY_PRINTING
{ &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor,
mozilla::Module::MAIN_PROCESS_ONLY },
- { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceProxyConstructor,
+ { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingProxyConstructor,
mozilla::Module::CONTENT_PROCESS_ONLY },
#else
{ &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor },
diff --git a/embedding/components/printingui/ipc/PPrinting.ipdl b/embedding/components/printingui/ipc/PPrinting.ipdl
index 489ac5015e38..7a01a008e4e7 100644
--- a/embedding/components/printingui/ipc/PPrinting.ipdl
+++ b/embedding/components/printingui/ipc/PPrinting.ipdl
@@ -102,6 +102,10 @@ parent:
PPrintProgressDialog();
+ sync SavePrintSettings(PrintData settings, bool usePrinterNamePrefix,
+ uint32_t flags)
+ returns(nsresult rv);
+
child:
__delete__();
};
diff --git a/embedding/components/printingui/ipc/PrintingParent.cpp b/embedding/components/printingui/ipc/PrintingParent.cpp
index 2457c6f54410..f051c237beb8 100644
--- a/embedding/components/printingui/ipc/PrintingParent.cpp
+++ b/embedding/components/printingui/ipc/PrintingParent.cpp
@@ -12,6 +12,7 @@
#include "nsIPrintingPromptService.h"
#include "nsIPrintOptions.h"
#include "nsIPrintProgressParams.h"
+#include "nsIPrintSettingsService.h"
#include "nsIServiceManager.h"
#include "nsIWebProgressListener.h"
#include "PrintingParent.h"
@@ -111,6 +112,31 @@ PrintingParent::RecvShowPrintDialog(PBrowserParent* parent,
return true;
}
+bool
+PrintingParent::RecvSavePrintSettings(const PrintData& aData,
+ const bool& aUsePrinterNamePrefix,
+ const uint32_t& aFlags,
+ nsresult* aResult)
+{
+ nsCOMPtr pss =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1", aResult);
+ NS_ENSURE_SUCCESS(*aResult, true);
+
+ nsCOMPtr po = do_QueryInterface(pss, aResult);
+ NS_ENSURE_SUCCESS(*aResult, true);
+
+ nsCOMPtr settings;
+ *aResult = po->CreatePrintSettings(getter_AddRefs(settings));
+ NS_ENSURE_SUCCESS(*aResult, true);
+
+ *aResult = po->DeserializeToPrintSettings(aData, settings);
+ NS_ENSURE_SUCCESS(*aResult, true);
+
+ *aResult = pss->SavePrintSettingsToPrefs(settings, aUsePrinterNamePrefix, aFlags);
+
+ return true;
+}
+
PPrintProgressDialogParent*
PrintingParent::AllocPPrintProgressDialogParent()
{
diff --git a/embedding/components/printingui/ipc/PrintingParent.h b/embedding/components/printingui/ipc/PrintingParent.h
index e3fccc0a2d4d..af66100a9748 100644
--- a/embedding/components/printingui/ipc/PrintingParent.h
+++ b/embedding/components/printingui/ipc/PrintingParent.h
@@ -31,6 +31,12 @@ public:
PrintData* retVal,
bool* success);
+ virtual bool
+ RecvSavePrintSettings(const PrintData& aData,
+ const bool& aUsePrinterNamePrefix,
+ const uint32_t& aFlags,
+ nsresult* aResult);
+
virtual PPrintProgressDialogParent*
AllocPPrintProgressDialogParent();
diff --git a/embedding/components/printingui/ipc/moz.build b/embedding/components/printingui/ipc/moz.build
index 2ac8456baf68..3101e0c5dda8 100644
--- a/embedding/components/printingui/ipc/moz.build
+++ b/embedding/components/printingui/ipc/moz.build
@@ -4,13 +4,17 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+EXPORTS += [
+ 'nsPrintingProxy.h',
+]
+
EXPORTS.mozilla.embedding.printingui += [
'PrintingParent.h',
]
if CONFIG['NS_PRINTING']:
UNIFIED_SOURCES += [
- 'nsPrintingPromptServiceProxy.cpp',
+ 'nsPrintingProxy.cpp',
'PrintDataUtils.cpp',
'PrintingParent.cpp',
'PrintProgressDialogChild.cpp',
diff --git a/embedding/components/printingui/ipc/nsPrintingPromptServiceProxy.cpp b/embedding/components/printingui/ipc/nsPrintingProxy.cpp
similarity index 58%
rename from embedding/components/printingui/ipc/nsPrintingPromptServiceProxy.cpp
rename to embedding/components/printingui/ipc/nsPrintingProxy.cpp
index 532647cdf260..a2eb33915d58 100644
--- a/embedding/components/printingui/ipc/nsPrintingPromptServiceProxy.cpp
+++ b/embedding/components/printingui/ipc/nsPrintingProxy.cpp
@@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/unused.h"
@@ -11,7 +12,7 @@
#include "nsIDocShellTreeOwner.h"
#include "nsIPrintingPromptService.h"
#include "nsPIDOMWindow.h"
-#include "nsPrintingPromptServiceProxy.h"
+#include "nsPrintingProxy.h"
#include "nsPrintOptionsImpl.h"
#include "PrintDataUtils.h"
#include "PrintProgressDialogChild.h"
@@ -20,27 +21,50 @@ using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::embedding;
-NS_IMPL_ISUPPORTS(nsPrintingPromptServiceProxy, nsIPrintingPromptService)
+static StaticRefPtr sPrintingProxyInstance;
-nsPrintingPromptServiceProxy::nsPrintingPromptServiceProxy()
+NS_IMPL_ISUPPORTS(nsPrintingProxy, nsIPrintingPromptService)
+
+nsPrintingProxy::nsPrintingProxy()
{
}
-nsPrintingPromptServiceProxy::~nsPrintingPromptServiceProxy()
+nsPrintingProxy::~nsPrintingProxy()
{
}
+/* static */
+already_AddRefed
+nsPrintingProxy::GetInstance()
+{
+ if (!sPrintingProxyInstance) {
+ sPrintingProxyInstance = new nsPrintingProxy();
+ if (!sPrintingProxyInstance) {
+ return nullptr;
+ }
+ nsresult rv = sPrintingProxyInstance->Init();
+ if (NS_FAILED(rv)) {
+ sPrintingProxyInstance = nullptr;
+ return nullptr;
+ }
+ ClearOnShutdown(&sPrintingProxyInstance);
+ }
+
+ nsRefPtr inst = sPrintingProxyInstance.get();
+ return inst.forget();
+}
+
nsresult
-nsPrintingPromptServiceProxy::Init()
+nsPrintingProxy::Init()
{
mozilla::unused << ContentChild::GetSingleton()->SendPPrintingConstructor(this);
return NS_OK;
}
NS_IMETHODIMP
-nsPrintingPromptServiceProxy::ShowPrintDialog(nsIDOMWindow *parent,
- nsIWebBrowserPrint *webBrowserPrint,
- nsIPrintSettings *printSettings)
+nsPrintingProxy::ShowPrintDialog(nsIDOMWindow *parent,
+ nsIWebBrowserPrint *webBrowserPrint,
+ nsIPrintSettings *printSettings)
{
NS_ENSURE_ARG(parent);
NS_ENSURE_ARG(webBrowserPrint);
@@ -86,14 +110,14 @@ nsPrintingPromptServiceProxy::ShowPrintDialog(nsIDOMWindow *parent,
}
NS_IMETHODIMP
-nsPrintingPromptServiceProxy::ShowProgress(nsIDOMWindow* parent,
- nsIWebBrowserPrint* webBrowserPrint, // ok to be null
- nsIPrintSettings* printSettings, // ok to be null
- nsIObserver* openDialogObserver, // ok to be null
- bool isForPrinting,
- nsIWebProgressListener** webProgressListener,
- nsIPrintProgressParams** printProgressParams,
- bool* notifyOnOpen)
+nsPrintingProxy::ShowProgress(nsIDOMWindow* parent,
+ nsIWebBrowserPrint* webBrowserPrint, // ok to be null
+ nsIPrintSettings* printSettings, // ok to be null
+ nsIObserver* openDialogObserver, // ok to be null
+ bool isForPrinting,
+ nsIWebProgressListener** webProgressListener,
+ nsIPrintProgressParams** printProgressParams,
+ bool* notifyOnOpen)
{
NS_ENSURE_ARG(parent);
NS_ENSURE_ARG(webProgressListener);
@@ -129,37 +153,56 @@ nsPrintingPromptServiceProxy::ShowProgress(nsIDOMWindow* parent,
}
NS_IMETHODIMP
-nsPrintingPromptServiceProxy::ShowPageSetup(nsIDOMWindow *parent,
- nsIPrintSettings *printSettings,
- nsIObserver *aObs)
+nsPrintingProxy::ShowPageSetup(nsIDOMWindow *parent,
+ nsIPrintSettings *printSettings,
+ nsIObserver *aObs)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
-nsPrintingPromptServiceProxy::ShowPrinterProperties(nsIDOMWindow *parent,
- const char16_t *printerName,
- nsIPrintSettings *printSettings)
+nsPrintingProxy::ShowPrinterProperties(nsIDOMWindow *parent,
+ const char16_t *printerName,
+ nsIPrintSettings *printSettings)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
+nsresult
+nsPrintingProxy::SavePrintSettings(nsIPrintSettings* aPS,
+ bool aUsePrinterNamePrefix,
+ uint32_t aFlags)
+{
+ nsresult rv;
+ nsCOMPtr po =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PrintData settings;
+ rv = po->SerializeToPrintData(aPS, nullptr, &settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ unused << SendSavePrintSettings(settings, aUsePrinterNamePrefix, aFlags,
+ &rv);
+ return rv;
+}
+
PPrintProgressDialogChild*
-nsPrintingPromptServiceProxy::AllocPPrintProgressDialogChild()
+nsPrintingProxy::AllocPPrintProgressDialogChild()
{
// The parent process will never initiate the PPrintProgressDialog
// protocol connection, so no need to provide an allocator here.
NS_NOTREACHED("Allocator for PPrintProgressDialogChild should not be "
- "called on nsPrintingPromptServiceProxy.");
+ "called on nsPrintingProxy.");
return nullptr;
}
bool
-nsPrintingPromptServiceProxy::DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor)
+nsPrintingProxy::DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor)
{
// The parent process will never initiate the PPrintProgressDialog
// protocol connection, so no need to provide an deallocator here.
NS_NOTREACHED("Deallocator for PPrintProgressDialogChild should not be "
- "called on nsPrintingPromptServiceProxy.");
+ "called on nsPrintingProxy.");
return false;
}
diff --git a/embedding/components/printingui/ipc/nsPrintingPromptServiceProxy.h b/embedding/components/printingui/ipc/nsPrintingProxy.h
similarity index 59%
rename from embedding/components/printingui/ipc/nsPrintingPromptServiceProxy.h
rename to embedding/components/printingui/ipc/nsPrintingProxy.h
index 317117e86803..3fc91b31b4c4 100644
--- a/embedding/components/printingui/ipc/nsPrintingPromptServiceProxy.h
+++ b/embedding/components/printingui/ipc/nsPrintingProxy.h
@@ -3,25 +3,31 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#ifndef __nsPrintingPromptServiceProxy_h
-#define __nsPrintingPromptServiceProxy_h
+#ifndef __nsPrintingProxy_h
+#define __nsPrintingProxy_h
#include "nsIPrintingPromptService.h"
#include "mozilla/embedding/PPrintingChild.h"
-class nsPrintingPromptServiceProxy: public nsIPrintingPromptService,
- public mozilla::embedding::PPrintingChild
+class nsPrintingProxy: public nsIPrintingPromptService,
+ public mozilla::embedding::PPrintingChild
{
- virtual ~nsPrintingPromptServiceProxy();
+ virtual ~nsPrintingProxy();
public:
- nsPrintingPromptServiceProxy();
+ nsPrintingProxy();
+
+ static already_AddRefed GetInstance();
nsresult Init();
NS_DECL_ISUPPORTS
NS_DECL_NSIPRINTINGPROMPTSERVICE
+ nsresult SavePrintSettings(nsIPrintSettings* aPS,
+ bool aUsePrinterNamePrefix,
+ uint32_t aFlags);
+
virtual PPrintProgressDialogChild*
AllocPPrintProgressDialogChild() MOZ_OVERRIDE;
diff --git a/gfx/2d/BasePoint.h b/gfx/2d/BasePoint.h
index 4bd60275f7f5..a09390946a27 100644
--- a/gfx/2d/BasePoint.h
+++ b/gfx/2d/BasePoint.h
@@ -90,6 +90,12 @@ struct BasePoint {
return true;
}
+ void Clamp(T aMaxAbsValue)
+ {
+ x = std::max(std::min(x, aMaxAbsValue), -aMaxAbsValue);
+ y = std::max(std::min(y, aMaxAbsValue), -aMaxAbsValue);
+ }
+
friend std::ostream& operator<<(std::ostream& stream, const BasePoint& aPoint) {
return stream << '(' << aPoint.x << ',' << aPoint.y << ')';
}
diff --git a/gfx/layers/apz/public/GeckoContentController.h b/gfx/layers/apz/public/GeckoContentController.h
index c479236e7667..d901647f7bad 100644
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -30,6 +30,14 @@ public:
*/
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
+ /**
+ * Requests handling of a scroll snapping at the end of a fling gesture for
+ * the scrollable frame with the given scroll id. aDestination specifies the
+ * expected landing position of the fling if no snapping were to be performed.
+ */
+ virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination) = 0;
+
/**
* Acknowledges the recipt of a scroll offset update for the scrollable
* frame with the given scroll id. This is used to maintain consistency
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp
index e6dc76114d43..a835d5bef259 100644
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1980,9 +1980,37 @@ void AsyncPanZoomController::AcceptFling(const ParentLayerPoint& aVelocity,
mX.SetVelocity(mX.GetVelocity() + aVelocity.x);
mY.SetVelocity(mY.GetVelocity() + aVelocity.y);
SetState(FLING);
- StartAnimation(new FlingAnimation(*this,
+ FlingAnimation *fling = new FlingAnimation(*this,
aOverscrollHandoffChain,
- !aHandoff)); // only apply acceleration if this is an initial fling
+ !aHandoff); // only apply acceleration if this is an initial fling
+
+ float friction = gfxPrefs::APZFlingFriction();
+ ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity());
+ ParentLayerPoint predictedDelta;
+ // "-velocity / log(1.0 - friction)" is the integral of the deceleration
+ // curve modeled for flings in the "Axis" class.
+ if (velocity.x != 0.0f) {
+ predictedDelta.x = -velocity.x / log(1.0 - friction);
+ }
+ if (velocity.y != 0.0f) {
+ predictedDelta.y = -velocity.y / log(1.0 - friction);
+ }
+ CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom();
+
+ nsRefPtr controller = GetGeckoContentController();
+ if (controller) {
+ APZC_LOG("%p fling snapping. friction: %f velocity: %f, %f "
+ "predictedDelta: %f, %f position: %f, %f "
+ "predictedDestination: %f, %f\n",
+ this, friction, velocity.x, velocity.y, (float)predictedDelta.x,
+ (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x,
+ (float)mFrameMetrics.GetScrollOffset().y,
+ (float)predictedDestination.x, (float)predictedDestination.y);
+ controller->RequestFlingSnap(mFrameMetrics.GetScrollId(),
+ predictedDestination);
+ }
+
+ StartAnimation(fling);
}
bool AsyncPanZoomController::AttemptFling(ParentLayerPoint aVelocity,
@@ -3070,7 +3098,7 @@ void AsyncPanZoomController::ShareCompositorFrameMetrics() {
// Send the shared memory handle and cross process handle to the content
// process by an asynchronous ipc call. Include the APZC unique ID
// so the content process know which APZC sent this shared FrameMetrics.
- if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mAPZCId)) {
+ if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mLayersId, mAPZCId)) {
APZC_LOG("%p failed to share FrameMetrics with content process.", this);
}
}
diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp
index 3a9e93f41c37..7182a42e9089 100644
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -264,6 +264,46 @@ APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK);
}
+class FlingSnapEvent : public nsRunnable
+{
+ typedef mozilla::layers::FrameMetrics::ViewID ViewID;
+
+public:
+ FlingSnapEvent(const ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination)
+ : mScrollId(aScrollId)
+ , mDestination(aDestination)
+ {
+ }
+
+ NS_IMETHOD Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(mScrollId);
+ if (sf) {
+ sf->FlingSnap(mDestination);
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ ViewID mScrollId;
+ mozilla::CSSPoint mDestination;
+};
+
+void
+APZCCallbackHelper::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination)
+{
+ nsCOMPtr r1 = new FlingSnapEvent(aScrollId, aDestination);
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(r1);
+ } else {
+ r1->Run();
+ }
+}
+
class AcknowledgeScrollUpdateEvent : public nsRunnable
{
typedef mozilla::layers::FrameMetrics::ViewID ViewID;
diff --git a/gfx/layers/apz/util/APZCCallbackHelper.h b/gfx/layers/apz/util/APZCCallbackHelper.h
index 27045eacf904..e9f4270f1441 100644
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -81,6 +81,14 @@ public:
uint32_t* aPresShellIdOut,
FrameMetrics::ViewID* aViewIdOut);
+ /* Tell layout to perform scroll snapping for the scrollable frame with the
+ * given scroll id. aDestination specifies the expected landing position of
+ * a current fling or scrolling animation that should be used to select
+ * the scroll snap point.
+ */
+ static void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination);
+
/* Tell layout that we received the scroll offset update for the given view ID, so
that it accepts future scroll offset updates from APZ. */
static void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp
index 07dd7bcd2305..631ff63feee3 100644
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -80,6 +80,13 @@ ChromeProcessController::PostDelayedTask(Task* aTask, int aDelayMs)
MessageLoop::current()->PostDelayedTask(FROM_HERE, aTask, aDelayMs);
}
+void
+ChromeProcessController::RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination)
+{
+ APZCCallbackHelper::RequestFlingSnap(aScrollId, aDestination);
+}
+
void
ChromeProcessController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration)
diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h
index 0fc8ec5bfd9d..526680d8293f 100644
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -37,6 +37,8 @@ public:
// GeckoContentController interface
virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
virtual void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE;
+ virtual void RequestFlingSnap(const FrameMetrics::ViewID& aScrollId,
+ const mozilla::CSSPoint& aDestination) MOZ_OVERRIDE;
virtual void AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aScrollId,
const uint32_t& aScrollGeneration) MOZ_OVERRIDE;
diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp
index e1b4246294ff..a6823d4b1b46 100644
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -360,7 +360,14 @@ BufferTextureHost::BufferTextureHost(gfx::SurfaceFormat aFormat,
, mUpdateSerial(1)
, mLocked(false)
, mNeedsFullUpdate(false)
-{}
+{
+ if (aFlags & TextureFlags::COMPONENT_ALPHA) {
+ // One texture of a component alpha texture pair will start out all white.
+ // This hack allows us to easily make sure that white will be uploaded.
+ // See bug 1138934
+ mNeedsFullUpdate = true;
+ }
+}
void
BufferTextureHost::InitSize()
diff --git a/gfx/layers/ipc/CompositorChild.cpp b/gfx/layers/ipc/CompositorChild.cpp
index 3045c3afe15b..bd48f99b6831 100644
--- a/gfx/layers/ipc/CompositorChild.cpp
+++ b/gfx/layers/ipc/CompositorChild.cpp
@@ -121,14 +121,27 @@ CompositorChild::AllocPLayerTransactionChild(const nsTArray& aBac
bool*)
{
MOZ_ASSERT(mCanSend);
- LayerTransactionChild* c = new LayerTransactionChild();
+ LayerTransactionChild* c = new LayerTransactionChild(aId);
c->AddIPDLReference();
return c;
}
+/*static*/ PLDHashOperator
+CompositorChild::RemoveSharedMetricsForLayersId(const uint64_t& aKey,
+ nsAutoPtr& aData,
+ void* aLayerTransactionChild)
+{
+ uint64_t childId = static_cast(aLayerTransactionChild)->GetId();
+ if (aData->GetLayersId() == childId) {
+ return PLDHashOperator::PL_DHASH_REMOVE;
+ }
+ return PLDHashOperator::PL_DHASH_NEXT;
+}
+
bool
CompositorChild::DeallocPLayerTransactionChild(PLayerTransactionChild* actor)
{
+ mFrameMetricsTable.Enumerate(RemoveSharedMetricsForLayersId, actor);
static_cast(actor)->ReleaseIPDLReference();
return true;
}
@@ -339,9 +352,11 @@ bool
CompositorChild::RecvSharedCompositorFrameMetrics(
const mozilla::ipc::SharedMemoryBasic::Handle& metrics,
const CrossProcessMutexHandle& handle,
+ const uint64_t& aLayersId,
const uint32_t& aAPZCId)
{
- SharedFrameMetricsData* data = new SharedFrameMetricsData(metrics, handle, aAPZCId);
+ SharedFrameMetricsData* data = new SharedFrameMetricsData(
+ metrics, handle, aLayersId, aAPZCId);
mFrameMetricsTable.Put(data->GetViewID(), data);
return true;
}
@@ -364,9 +379,11 @@ CompositorChild::RecvReleaseSharedCompositorFrameMetrics(
CompositorChild::SharedFrameMetricsData::SharedFrameMetricsData(
const ipc::SharedMemoryBasic::Handle& metrics,
const CrossProcessMutexHandle& handle,
- const uint32_t& aAPZCId) :
- mMutex(nullptr),
- mAPZCId(aAPZCId)
+ const uint64_t& aLayersId,
+ const uint32_t& aAPZCId)
+ : mMutex(nullptr)
+ , mLayersId(aLayersId)
+ , mAPZCId(aAPZCId)
{
mBuffer = new ipc::SharedMemoryBasic(metrics);
mBuffer->Map(sizeof(FrameMetrics));
@@ -403,6 +420,12 @@ CompositorChild::SharedFrameMetricsData::GetViewID()
return frame->GetScrollId();
}
+uint64_t
+CompositorChild::SharedFrameMetricsData::GetLayersId() const
+{
+ return mLayersId;
+}
+
uint32_t
CompositorChild::SharedFrameMetricsData::GetAPZCId()
{
diff --git a/gfx/layers/ipc/CompositorChild.h b/gfx/layers/ipc/CompositorChild.h
index 171d3cc00bfb..0fa13ea2152d 100644
--- a/gfx/layers/ipc/CompositorChild.h
+++ b/gfx/layers/ipc/CompositorChild.h
@@ -128,6 +128,7 @@ private:
virtual bool RecvSharedCompositorFrameMetrics(const mozilla::ipc::SharedMemoryBasic::Handle& metrics,
const CrossProcessMutexHandle& handle,
+ const uint64_t& aLayersId,
const uint32_t& aAPZCId) MOZ_OVERRIDE;
virtual bool RecvReleaseSharedCompositorFrameMetrics(const ViewID& aId,
@@ -142,12 +143,14 @@ private:
SharedFrameMetricsData(
const mozilla::ipc::SharedMemoryBasic::Handle& metrics,
const CrossProcessMutexHandle& handle,
+ const uint64_t& aLayersId,
const uint32_t& aAPZCId);
~SharedFrameMetricsData();
void CopyFrameMetrics(FrameMetrics* aFrame);
FrameMetrics::ViewID GetViewID();
+ uint64_t GetLayersId() const;
uint32_t GetAPZCId();
private:
@@ -155,10 +158,15 @@ private:
// the shared FrameMetrics
nsRefPtr mBuffer;
CrossProcessMutex* mMutex;
+ uint64_t mLayersId;
// Unique ID of the APZC that is sharing the FrameMetrics
uint32_t mAPZCId;
};
+ static PLDHashOperator RemoveSharedMetricsForLayersId(const uint64_t& aKey,
+ nsAutoPtr& aData,
+ void* aLayerTransactionChild);
+
nsRefPtr mLayerManager;
// The ViewID of the FrameMetrics is used as the key for this hash table.
diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp
index e137818e1518..44244ebd0be9 100644
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -1449,6 +1449,22 @@ CompositorParent::PostInsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp)
}
}
+/* static */ void
+CompositorParent::RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
+{
+ EnsureLayerTreeMapReady();
+ MonitorAutoLock lock(*sIndirectLayerTreesLock);
+ sIndirectLayerTrees[aLayersId].mLayerTreeReadyObserver = aObserver;
+}
+
+/* static */ void
+CompositorParent::RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver)
+{
+ EnsureLayerTreeMapReady();
+ MonitorAutoLock lock(*sIndirectLayerTreesLock);
+ sIndirectLayerTrees[aLayersId].mLayerTreeClearedObserver = aObserver;
+}
+
/**
* This class handles layer updates pushed directly from child
* processes to the compositor thread. It's associated with a
@@ -1527,6 +1543,7 @@ public:
uint32_t aPaintSequenceNumber,
bool aIsRepeatTransaction) MOZ_OVERRIDE;
virtual void ForceComposite(LayerTransactionParent* aLayerTree) MOZ_OVERRIDE;
+ virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) MOZ_OVERRIDE;
virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
const TimeStamp& aTime) MOZ_OVERRIDE;
virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) MOZ_OVERRIDE;
@@ -1760,6 +1777,12 @@ CrossProcessCompositorParent::ShadowLayersUpdated(
mNotifyAfterRemotePaint = false;
}
+ if (state->mLayerTreeReadyObserver) {
+ nsRefPtr observer = state->mLayerTreeReadyObserver;
+ state->mLayerTreeReadyObserver = nullptr;
+ observer->ObserveUpdate(id, true);
+ }
+
aLayerTree->SetPendingTransactionId(aTransactionId);
}
@@ -1859,6 +1882,23 @@ CrossProcessCompositorParent::ForceComposite(LayerTransactionParent* aLayerTree)
}
}
+void
+CrossProcessCompositorParent::NotifyClearCachedResources(LayerTransactionParent* aLayerTree)
+{
+ uint64_t id = aLayerTree->GetId();
+ MOZ_ASSERT(id != 0);
+
+ nsRefPtr observer;
+ { // scope lock
+ MonitorAutoLock lock(*sIndirectLayerTreesLock);
+ observer = sIndirectLayerTrees[id].mLayerTreeClearedObserver;
+ sIndirectLayerTrees[id].mLayerTreeClearedObserver = nullptr;
+ }
+ if (observer) {
+ observer->ObserveUpdate(id, false);
+ }
+}
+
bool
CrossProcessCompositorParent::SetTestSampleTime(
LayerTransactionParent* aLayerTree, const TimeStamp& aTime)
diff --git a/gfx/layers/ipc/CompositorParent.h b/gfx/layers/ipc/CompositorParent.h
index ce80a0300a9e..88c87fc71b1b 100644
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -130,6 +130,17 @@ private:
CancelableTask* mSetNeedsCompositeTask;
};
+class CompositorUpdateObserver
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorUpdateObserver);
+
+ virtual void ObserveUpdate(uint64_t aLayersId, bool aActive) = 0;
+
+protected:
+ virtual ~CompositorUpdateObserver() {}
+};
+
class CompositorParent MOZ_FINAL : public PCompositorParent,
public ShadowLayersManager
{
@@ -305,6 +316,8 @@ public:
LayerTransactionParent* mLayerTree;
nsTArray mPluginData;
bool mUpdatedPluginDataAvailable;
+ nsRefPtr mLayerTreeReadyObserver;
+ nsRefPtr mLayerTreeClearedObserver;
};
/**
@@ -319,6 +332,9 @@ public:
*/
static void PostInsertVsyncProfilerMarker(mozilla::TimeStamp aVsyncTimestamp);
+ static void RequestNotifyLayerTreeReady(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
+ static void RequestNotifyLayerTreeCleared(uint64_t aLayersId, CompositorUpdateObserver* aObserver);
+
float ComputeRenderIntegrity();
/**
diff --git a/gfx/layers/ipc/LayerTransactionChild.h b/gfx/layers/ipc/LayerTransactionChild.h
index 2a118053eeb4..e3e9859c7f56 100644
--- a/gfx/layers/ipc/LayerTransactionChild.h
+++ b/gfx/layers/ipc/LayerTransactionChild.h
@@ -51,11 +51,14 @@ public:
PTextureChild* aTexture,
const FenceHandle& aFence);
+ uint64_t GetId() const { return mId; }
+
protected:
- LayerTransactionChild()
+ explicit LayerTransactionChild(const uint64_t& aId)
: mForwarder(nullptr)
, mIPCOpen(false)
, mDestroyed(false)
+ , mId(aId)
{}
~LayerTransactionChild() { }
@@ -90,6 +93,7 @@ protected:
ShadowLayerForwarder* mForwarder;
bool mIPCOpen;
bool mDestroyed;
+ uint64_t mId;
};
} // namespace layers
diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp
index a061dcd5cf1f..486a3a25b3a1 100644
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -843,6 +843,8 @@ LayerTransactionParent::RecvClearCachedResources()
// context, it's just a subtree root. We need to scope the clear
// of resources to exactly that subtree, so we specify it here.
mLayerManager->ClearCachedResources(mRoot);
+
+ mShadowLayersManager->NotifyClearCachedResources(this);
}
return true;
}
diff --git a/gfx/layers/ipc/PCompositor.ipdl b/gfx/layers/ipc/PCompositor.ipdl
index d5753e8d389b..39a9d64c38f3 100644
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -133,7 +133,7 @@ parent:
child:
// Send back Compositor Frame Metrics from APZCs so tiled layers can
// update progressively.
- async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, uint32_t aAPZCId);
+ async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, uint64_t aLayersId, uint32_t aAPZCId);
async ReleaseSharedCompositorFrameMetrics(ViewID aId, uint32_t aAPZCId);
};
diff --git a/gfx/layers/ipc/ShadowLayersManager.h b/gfx/layers/ipc/ShadowLayersManager.h
index 474aa2ca0fb6..16414e8226ac 100644
--- a/gfx/layers/ipc/ShadowLayersManager.h
+++ b/gfx/layers/ipc/ShadowLayersManager.h
@@ -29,6 +29,8 @@ public:
virtual AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aLayerTree) { return nullptr; }
+ virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) { }
+
virtual void ForceComposite(LayerTransactionParent* aLayerTree) { }
virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
const TimeStamp& aTime) { return true; }
diff --git a/gfx/tests/gtest/TestAsyncPanZoomController.cpp b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
index 776e33c874d4..8e3a182f81ba 100644
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -61,6 +61,7 @@ private:
class MockContentController : public GeckoContentController {
public:
MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
+ MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
diff --git a/gfx/thebes/gfxFontFamilyList.h b/gfx/thebes/gfxFontFamilyList.h
index 1906fba828bd..c1d189d92cc3 100644
--- a/gfx/thebes/gfxFontFamilyList.h
+++ b/gfx/thebes/gfxFontFamilyList.h
@@ -9,6 +9,7 @@
#include "nsDebug.h"
#include "nsISupportsImpl.h"
#include "nsString.h"
+#include "nsUnicharUtils.h"
#include "nsTArray.h"
#include "mozilla/MemoryReporting.h"
@@ -280,6 +281,26 @@ public:
}
}
+ // searches for a specific non-generic name, lowercase comparison
+ bool Contains(const nsAString& aFamilyName) const {
+ uint32_t len = mFontlist.Length();
+ nsAutoString fam(aFamilyName);
+ ToLowerCase(fam);
+ for (uint32_t i = 0; i < len; i++) {
+ const FontFamilyName& name = mFontlist[i];
+ if (name.mType != eFamily_named &&
+ name.mType != eFamily_named_quoted) {
+ continue;
+ }
+ nsAutoString listname(name.mName);
+ ToLowerCase(listname);
+ if (listname.Equals(fam)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
FontFamilyType GetDefaultFontType() const { return mDefaultFontType; }
void SetDefaultFontType(FontFamilyType aType) {
NS_ASSERTION(aType == eFamily_none || aType == eFamily_serif ||
diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp
index 84d127d57b80..f8369de19ac6 100644
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -63,7 +63,8 @@ gfxPlatformGtk::gfxPlatformGtk()
if (!sFontconfigUtils)
sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
#ifdef MOZ_X11
- sUseXRender = mozilla::Preferences::GetBool("gfx.xrender.enabled");
+ sUseXRender = (GDK_IS_X11_DISPLAY(gdk_display_get_default())) ?
+ mozilla::Preferences::GetBool("gfx.xrender.enabled") : false;
#endif
uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
@@ -276,11 +277,15 @@ gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
size = 0;
#ifdef MOZ_X11
+ GdkDisplay *display = gdk_display_get_default();
+ if (!GDK_IS_X11_DISPLAY(display))
+ return;
+
const char EDID1_ATOM_NAME[] = "XFree86_DDC_EDID1_RAWDATA";
const char ICC_PROFILE_ATOM_NAME[] = "_ICC_PROFILE";
Atom edidAtom, iccAtom;
- Display *dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ Display *dpy = GDK_DISPLAY_XDISPLAY(display);
// In xpcshell tests, we never initialize X and hence don't have a Display.
// In this case, there's no output colour management to be done, so we just
// return with nullptr.
diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h
index a72c56f943db..cf310d9c83be 100644
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -319,6 +319,8 @@ private:
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.damping-ratio", ScrollBehaviorDampingRatio, float, 1.0f);
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled", ScrollBehaviorEnabled, bool, false);
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f);
+ DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f);
+ DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000);
DECL_GFX_PREF(Once, "layout.css.touch_action.enabled", TouchActionEnabled, bool, false);
DECL_GFX_PREF(Once, "layout.frame_rate", LayoutFrameRate, int32_t, -1);
DECL_GFX_PREF(Live, "layout.display-list.dump", LayoutDumpDisplayList, bool, false);
diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp
index f5ba248bd24e..e068b8c37947 100644
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1802,6 +1802,22 @@ gfxFontGroup::FamilyFace::CheckState(bool& aSkipDrawing)
}
}
+bool
+gfxFontGroup::FamilyFace::EqualsUserFont(const gfxUserFontEntry* aUserFont) const
+{
+ gfxFontEntry* fe = FontEntry();
+ // if there's a font, the entry is the underlying platform font
+ if (mFontCreated) {
+ gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry();
+ if (pfe == fe) {
+ return true;
+ }
+ } else if (fe == aUserFont) {
+ return true;
+ }
+ return false;
+}
+
bool
gfxFontGroup::FontLoadingForFamily(gfxFontFamily* aFamily, uint32_t aCh) const
{
@@ -3030,6 +3046,21 @@ gfxFontGroup::UpdateUserFonts()
}
}
+bool
+gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont)
+{
+ UpdateUserFonts();
+ // search through the fonts list for a specific user font
+ uint32_t len = mFonts.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ FamilyFace& ff = mFonts[i];
+ if (ff.EqualsUserFont(aUserFont)) {
+ return true;
+ }
+ }
+ return false;
+}
+
struct PrefFontCallbackData {
explicit PrefFontCallbackData(nsTArray >& aFamiliesArray)
: mPrefFamilies(aFamiliesArray)
diff --git a/gfx/thebes/gfxTextRun.h b/gfx/thebes/gfxTextRun.h
index f9c8d83679ab..08bf8a200105 100644
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -24,6 +24,7 @@
class gfxContext;
class gfxFontGroup;
+class gfxUserFontEntry;
class gfxUserFontSet;
class gfxTextContextPaint;
class nsIAtom;
@@ -864,6 +865,9 @@ public:
// caches need updating.
virtual void UpdateUserFonts();
+ // search for a specific userfont in the list of fonts
+ bool ContainsUserFont(const gfxUserFontEntry* aUserFont);
+
bool ShouldSkipDrawing() const {
return mSkipDrawing;
}
@@ -1002,6 +1006,8 @@ protected:
mLoading = false;
}
+ bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const;
+
private:
nsRefPtr mFamily;
// either a font or a font entry exists
diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp
index f1dd9f94e4b4..21a65f3cfa9e 100644
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -920,6 +920,21 @@ gfxUserFontSet::LookupFamily(const nsAString& aFamilyName) const
return mFontFamilies.GetWeak(key);
}
+bool
+gfxUserFontSet::ContainsUserFontSetFonts(const FontFamilyList& aFontList) const
+{
+ for (const FontFamilyName& name : aFontList.GetFontlist()) {
+ if (name.mType != eFamily_named &&
+ name.mType != eFamily_named_quoted) {
+ continue;
+ }
+ if (LookupFamily(name.mName)) {
+ return true;
+ }
+ }
+ return false;
+}
+
gfxUserFontFamily*
gfxUserFontSet::GetFamily(const nsAString& aFamilyName)
{
diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h
index 20fce3d6ec0c..422b4c10c77f 100644
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -7,6 +7,7 @@
#define GFX_USER_FONT_SET_H
#include "gfxFont.h"
+#include "gfxFontFamilyList.h"
#include "nsRefPtrHashtable.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
@@ -238,6 +239,9 @@ public:
// the given name
gfxUserFontFamily* LookupFamily(const nsAString& aName) const;
+ // Look up names in a fontlist and return true if any are in the set
+ bool ContainsUserFontSetFonts(const mozilla::FontFamilyList& aFontList) const;
+
// Lookup a font entry for a given style, returns null if not loaded.
// aFamily must be a family returned by our LookupFamily method.
// (only used by gfxPangoFontGroup for now)
@@ -553,7 +557,7 @@ public:
virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle,
bool aNeedsBold);
- gfxFontEntry* GetPlatformFontEntry() { return mPlatformFontEntry; }
+ gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
// is the font loading or loaded, or did it fail?
UserFontLoadState LoadState() const { return mUserFontLoadState; }
diff --git a/image/decoders/icon/gtk/nsIconChannel.cpp b/image/decoders/icon/gtk/nsIconChannel.cpp
index 86e7bfc77851..0af0cdf3e209 100644
--- a/image/decoders/icon/gtk/nsIconChannel.cpp
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -10,17 +10,6 @@
#include "mozilla/Endian.h"
#include
-#ifdef MOZ_ENABLE_GNOMEUI
-// Older versions of these headers seem to be missing an extern "C"
-extern "C" {
-#include
-#include
-#include
-
-#include
-#include
-}
-#endif
#ifdef MOZ_ENABLE_GIO
#include
#endif
@@ -43,41 +32,6 @@ NS_IMPL_ISUPPORTS(nsIconChannel,
nsIRequest,
nsIChannel)
-#ifdef MOZ_ENABLE_GNOMEUI
-// These let us have a soft dependency on libgnomeui rather than a hard one.
-// These are just basically the prototypes of the functions in the libraries.
-typedef char* (*_GnomeIconLookup_fn)(GtkIconTheme* icon_theme,
- GnomeThumbnailFactory* thumbnail_factory,
- const char* file_uri,
- const char* custom_icon,
- GnomeVFSFileInfo* file_info,
- const char* mime_type,
- GnomeIconLookupFlags flags,
- GnomeIconLookupResultFlags* result);
-typedef GnomeIconTheme* (*_GnomeIconThemeNew_fn)(void);
-typedef int (*_GnomeInit_fn)(const char* app_id, const char* app_version,
- int argc, char** argv,
- const struct poptOption* options,
- int flags, poptContext* return_ctx);
-typedef GnomeProgram* (*_GnomeProgramGet_fn)(void);
-typedef GnomeVFSResult (*_GnomeVFSGetFileInfo_fn)(const gchar* text_uri,
- GnomeVFSFileInfo* info,
- GnomeVFSFileInfoOptions options);
-typedef void (*_GnomeVFSFileInfoClear_fn)(GnomeVFSFileInfo* info);
-
-static PRLibrary* gLibGnomeUI = nullptr;
-static PRLibrary* gLibGnome = nullptr;
-static PRLibrary* gLibGnomeVFS = nullptr;
-static bool gTriedToLoadGnomeLibs = false;
-
-static _GnomeIconLookup_fn _gnome_icon_lookup = nullptr;
-static _GnomeIconThemeNew_fn _gnome_icon_theme_new = nullptr;
-static _GnomeInit_fn _gnome_init = nullptr;
-static _GnomeProgramGet_fn _gnome_program_get = nullptr;
-static _GnomeVFSGetFileInfo_fn _gnome_vfs_get_file_info = nullptr;
-static _GnomeVFSFileInfoClear_fn _gnome_vfs_file_info_clear = nullptr;
-#endif //MOZ_ENABLE_GNOMEUI
-
static nsresult
moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
nsIChannel** aChannel)
@@ -162,9 +116,6 @@ moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
static GtkWidget* gProtoWindow = nullptr;
static GtkWidget* gStockImageWidget = nullptr;
-#ifdef MOZ_ENABLE_GNOMEUI
-static GnomeIconTheme* gIconTheme = nullptr;
-#endif //MOZ_ENABLE_GNOMEUI
static void
ensure_stock_image_widget()
@@ -183,100 +134,6 @@ ensure_stock_image_widget()
}
}
-#ifdef MOZ_ENABLE_GNOMEUI
-static nsresult
-ensure_libgnomeui()
-{
- // Attempt to get the libgnomeui symbol references. We do it this way
- // so that stock icons from Init() don't get held back by InitWithGnome()'s
- // libgnomeui dependency.
- if (!gTriedToLoadGnomeLibs) {
- gLibGnomeUI = PR_LoadLibrary("libgnomeui-2.so.0");
- if (!gLibGnomeUI) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- _gnome_init =
- (_GnomeInit_fn)PR_FindFunctionSymbol(gLibGnomeUI,
- "gnome_init_with_popt_table");
- _gnome_icon_theme_new =
- (_GnomeIconThemeNew_fn)PR_FindFunctionSymbol(gLibGnomeUI,
- "gnome_icon_theme_new");
- _gnome_icon_lookup =
- (_GnomeIconLookup_fn)PR_FindFunctionSymbol(gLibGnomeUI,
- "gnome_icon_lookup");
-
- if (!_gnome_init || !_gnome_icon_theme_new || !_gnome_icon_lookup) {
- PR_UnloadLibrary(gLibGnomeUI);
- gLibGnomeUI = nullptr;
- return NS_ERROR_NOT_AVAILABLE;
- }
- }
-
- if (!gLibGnomeUI) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- return NS_OK;
-}
-
-static nsresult
-ensure_libgnome()
-{
- if (!gTriedToLoadGnomeLibs) {
- gLibGnome = PR_LoadLibrary("libgnome-2.so.0");
- if (!gLibGnome) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- _gnome_program_get =
- (_GnomeProgramGet_fn)PR_FindFunctionSymbol(gLibGnome,
- "gnome_program_get");
- if (!_gnome_program_get) {
- PR_UnloadLibrary(gLibGnome);
- gLibGnome = nullptr;
- return NS_ERROR_NOT_AVAILABLE;
- }
- }
-
- if (!gLibGnome) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- return NS_OK;
-}
-
-static nsresult
-ensure_libgnomevfs()
-{
- if (!gTriedToLoadGnomeLibs) {
- gLibGnomeVFS = PR_LoadLibrary("libgnomevfs-2.so.0");
- if (!gLibGnomeVFS) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- _gnome_vfs_get_file_info =
- (_GnomeVFSGetFileInfo_fn)PR_FindFunctionSymbol(gLibGnomeVFS,
- "gnome_vfs_get_file_info");
- _gnome_vfs_file_info_clear =
- (_GnomeVFSFileInfoClear_fn)PR_FindFunctionSymbol(gLibGnomeVFS,
- "gnome_vfs_file_"
- "info_clear");
- if (!_gnome_vfs_get_file_info || !_gnome_vfs_file_info_clear) {
- PR_UnloadLibrary(gLibGnomeVFS);
- gLibGnomeVFS = nullptr;
- return NS_ERROR_NOT_AVAILABLE;
- }
- }
-
- if (!gLibGnomeVFS) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- return NS_OK;
-}
-#endif //MOZ_ENABLE_GNOMEUI
-
static GtkIconSize
moz_gtk_icon_size(const char* name)
{
@@ -307,7 +164,7 @@ moz_gtk_icon_size(const char* name)
return GTK_ICON_SIZE_MENU;
}
-#if defined(MOZ_ENABLE_GNOMEUI) || defined(MOZ_ENABLE_GIO)
+#ifdef MOZ_ENABLE_GIO
static int32_t
GetIconSize(nsIMozIconURI* aIconURI)
{
@@ -346,149 +203,7 @@ ScaleIconBuf(GdkPixbuf** aBuf, int32_t iconSize)
}
return NS_OK;
}
-#endif
-#ifdef MOZ_ENABLE_GNOMEUI
-nsresult
-nsIconChannel::InitWithGnome(nsIMozIconURI* aIconURI)
-{
- nsresult rv;
-
- if (NS_FAILED(ensure_libgnomeui()) || NS_FAILED(ensure_libgnome()) ||
- NS_FAILED(ensure_libgnomevfs())) {
- gTriedToLoadGnomeLibs = true;
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- gTriedToLoadGnomeLibs = true;
-
- if (!_gnome_program_get()) {
- // Get the brandShortName from the string bundle to pass to GNOME
- // as the application name. This may be used for things such as
- // the title of grouped windows in the panel.
- nsCOMPtr bundleService =
- do_GetService(NS_STRINGBUNDLE_CONTRACTID);
-
- NS_ASSERTION(bundleService, "String bundle service must be present!");
-
- nsCOMPtr bundle;
- bundleService->CreateBundle("chrome://branding/locale/brand.properties",
- getter_AddRefs(bundle));
- nsAutoString appName;
-
- if (bundle) {
- bundle->GetStringFromName(MOZ_UTF16("brandShortName"),
- getter_Copies(appName));
- } else {
- NS_WARNING(
- "brand.properties not present, using default application name");
- appName.AssignLiteral(MOZ_UTF16("Gecko"));
- }
-
- char* empty[] = { "" };
- _gnome_init(NS_ConvertUTF16toUTF8(appName).get(),
- "1.0", 1, empty, nullptr, 0, nullptr);
- }
-
- uint32_t iconSize = GetIconSize(aIconURI);
- nsAutoCString type;
- aIconURI->GetContentType(type);
-
- GnomeVFSFileInfo fileInfo = {0};
- fileInfo.refcount = 1; // In case some GnomeVFS function addrefs and
- // releases it
-
- nsAutoCString spec;
- nsCOMPtr url;
- rv = aIconURI->GetIconURL(getter_AddRefs(url));
- if (url) {
- url->GetAsciiSpec(spec);
- // Only ask gnome-vfs for a GnomeVFSFileInfo for file: uris, to avoid a
- // network request
- bool isFile;
- if (NS_SUCCEEDED(url->SchemeIs("file", &isFile)) && isFile) {
- _gnome_vfs_get_file_info(spec.get(), &fileInfo,
- GNOME_VFS_FILE_INFO_DEFAULT);
- }
- else {
- // The filename we get is UTF-8-compatible, which matches gnome
- // expectations.
- // See also:
- // http://lists.gnome.org/archives/gnome-vfs-list/2004-March/msg00049.html
- // "Whenever we can detect the charset used for the URI type we try to
- // convert it to/from utf8 automatically inside gnome-vfs."
- // I'll interpret that as "otherwise, this field is random junk".
- nsAutoCString name;
- url->GetFileName(name);
- fileInfo.name = g_strdup(name.get());
-
- if (!type.IsEmpty()) {
- fileInfo.valid_fields = GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
- fileInfo.mime_type = g_strdup(type.get());
- }
- }
- }
-
- if (type.IsEmpty()) {
- nsCOMPtr ms(do_GetService("@mozilla.org/mime;1"));
- if (ms) {
- nsAutoCString fileExt;
- aIconURI->GetFileExtension(fileExt);
- if (!fileExt.IsEmpty()) {
- ms->GetTypeFromExtension(fileExt, type);
- }
- }
- }
- // Get the icon theme
- if (!gIconTheme) {
- gIconTheme = _gnome_icon_theme_new();
-
- if (!gIconTheme) {
- _gnome_vfs_file_info_clear(&fileInfo);
- return NS_ERROR_NOT_AVAILABLE;
- }
- }
-
- char* name = _gnome_icon_lookup(gIconTheme, nullptr, spec.get(), nullptr,
- &fileInfo, type.get(),
- GNOME_ICON_LOOKUP_FLAGS_NONE, nullptr);
-
- _gnome_vfs_file_info_clear(&fileInfo);
- if (!name) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- // Get the default theme associated with the screen
- // Do NOT free.
- GtkIconTheme* theme = gtk_icon_theme_get_default();
- if (!theme) {
- g_free(name);
- return NS_ERROR_UNEXPECTED;
- }
-
- GError* err = nullptr;
- GdkPixbuf* buf = gtk_icon_theme_load_icon(theme, name, iconSize,
- (GtkIconLookupFlags)0, &err);
- g_free(name);
-
- if (!buf) {
- if (err) {
- g_error_free(err);
- }
- return NS_ERROR_UNEXPECTED;
- }
-
- rv = ScaleIconBuf(&buf, iconSize);
- NS_ENSURE_SUCCESS(rv, rv);
-
- rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
- getter_AddRefs(mRealChannel));
- g_object_unref(buf);
- return rv;
-}
-#endif // MOZ_ENABLE_GNOMEUI
-
-#ifdef MOZ_ENABLE_GIO
nsresult
nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI)
{
@@ -594,14 +309,10 @@ nsIconChannel::Init(nsIURI* aURI)
nsAutoCString stockIcon;
iconURI->GetStockIcon(stockIcon);
if (stockIcon.IsEmpty()) {
-#ifdef MOZ_ENABLE_GNOMEUI
- return InitWithGnome(iconURI);
-#else
#ifdef MOZ_ENABLE_GIO
return InitWithGIO(iconURI);
#else
return NS_ERROR_NOT_AVAILABLE;
-#endif
#endif
}
@@ -710,23 +421,4 @@ nsIconChannel::Shutdown() {
gProtoWindow = nullptr;
gStockImageWidget = nullptr;
}
-#ifdef MOZ_ENABLE_GNOMEUI
- if (gIconTheme) {
- g_object_unref(G_OBJECT(gIconTheme));
- gIconTheme = nullptr;
- }
- gTriedToLoadGnomeLibs = false;
- if (gLibGnomeUI) {
- PR_UnloadLibrary(gLibGnomeUI);
- gLibGnomeUI = nullptr;
- }
- if (gLibGnome) {
- PR_UnloadLibrary(gLibGnome);
- gLibGnome = nullptr;
- }
- if (gLibGnomeVFS) {
- PR_UnloadLibrary(gLibGnomeVFS);
- gLibGnomeVFS = nullptr;
- }
-#endif //MOZ_ENABLE_GNOMEUI
}
diff --git a/image/decoders/icon/gtk/nsIconChannel.h b/image/decoders/icon/gtk/nsIconChannel.h
index 273e97883cdd..3a1426ef7ef8 100644
--- a/image/decoders/icon/gtk/nsIconChannel.h
+++ b/image/decoders/icon/gtk/nsIconChannel.h
@@ -37,8 +37,6 @@ class nsIconChannel MOZ_FINAL : public nsIChannel
/// Will always be non-null after a successful Init.
nsCOMPtr mRealChannel;
- /// Called by Init if we need to use the gnomeui library.
- nsresult InitWithGnome(nsIMozIconURI* aURI);
nsresult InitWithGIO(nsIMozIconURI* aIconURI);
};
diff --git a/js/src/asmjs/AsmJSFrameIterator.cpp b/js/src/asmjs/AsmJSFrameIterator.cpp
index 8fc896aa8fd3..7e424b3fe7fd 100644
--- a/js/src/asmjs/AsmJSFrameIterator.cpp
+++ b/js/src/asmjs/AsmJSFrameIterator.cpp
@@ -115,8 +115,8 @@ AsmJSFrameIterator::computeLine(uint32_t *column) const
static const unsigned PushedRetAddr = 0;
static const unsigned PostStorePrePopFP = 0;
# endif
-static const unsigned PushedFP = 10;
-static const unsigned StoredFP = 14;
+static const unsigned PushedFP = 13;
+static const unsigned StoredFP = 20;
#elif defined(JS_CODEGEN_X86)
# if defined(DEBUG)
static const unsigned PushedRetAddr = 0;
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
index cdccb9ad77d6..d4c174eb6921 100644
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1007,6 +1007,37 @@ SaveStack(JSContext *cx, unsigned argc, jsval *vp)
return true;
}
+static bool
+CallFunctionWithAsyncStack(JSContext *cx, unsigned argc, jsval *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() != 3) {
+ JS_ReportError(cx, "The function takes exactly three arguments.");
+ return false;
+ }
+ if (!args[0].isObject() || !IsCallable(args[0])) {
+ JS_ReportError(cx, "The first argument should be a function.");
+ return false;
+ }
+ if (!args[1].isObject() || !args[1].toObject().is()) {
+ JS_ReportError(cx, "The second argument should be a SavedFrame.");
+ return false;
+ }
+ if (!args[2].isString() || args[2].toString()->empty()) {
+ JS_ReportError(cx, "The third argument should be a non-empty string.");
+ return false;
+ }
+
+ RootedObject function(cx, &args[0].toObject());
+ RootedObject stack(cx, &args[1].toObject());
+ RootedString asyncCause(cx, args[2].toString());
+
+ JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause);
+ return Call(cx, UndefinedHandleValue, function,
+ JS::HandleValueArray::empty(), args.rval());
+}
+
static bool
EnableTrackAllocations(JSContext *cx, unsigned argc, jsval *vp)
{
@@ -2438,6 +2469,13 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
" of frames. If 'compartment' is given, allocate the js::SavedFrame instances\n"
" with the given object's compartment."),
+ JS_FN_HELP("callFunctionWithAsyncStack", CallFunctionWithAsyncStack, 0, 0,
+"callFunctionWithAsyncStack(function, stack, asyncCause)",
+" Call 'function', using the provided stack as the async stack responsible\n"
+" for the call, and propagate its return value or the exception it throws.\n"
+" The function is called with no arguments, and 'this' is 'undefined'. The\n"
+" specified |asyncCause| is attached to the provided stack frame."),
+
JS_FN_HELP("enableTrackAllocations", EnableTrackAllocations, 0, 0,
"enableTrackAllocations()",
" Start capturing the JS stack at every allocation. Note that this sets an\n"
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
index 26eeeeda51ee..1df7b8039812 100644
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -2,6 +2,77 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// ES6 draft 20150304 %TypedArray%.prototype.copyWithin
+function TypedArrayCopyWithin(target, start, end = undefined) {
+ // This function is not generic.
+ if (!IsObject(this) || !IsTypedArray(this)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, target, start, end,
+ "TypedArrayCopyWithin");
+ }
+
+ // Bug 1101256: detachment checks mandated by ValidateTypedArray
+
+ // Steps 1-2.
+ var obj = this;
+
+ // Steps 3-4, modified for the typed array case.
+ var len = TypedArrayLength(obj);
+
+ assert(0 <= len && len <= 0x7FFFFFFF,
+ "assumed by some of the math below, see also the other assertions");
+
+ // Steps 5-7.
+ var relativeTarget = ToInteger(target);
+
+ var to = relativeTarget < 0 ? std_Math_max(len + relativeTarget, 0)
+ : std_Math_min(relativeTarget, len);
+
+ // Steps 8-10.
+ var relativeStart = ToInteger(start);
+
+ var from = relativeStart < 0 ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Steps 11-13.
+ var relativeEnd = end === undefined ? len
+ : ToInteger(end);
+
+ var final = relativeEnd < 0 ? std_Math_max(len + relativeEnd, 0)
+ : std_Math_min(relativeEnd, len);
+
+ // Step 14.
+ var count = std_Math_min(final - from, len - to);
+
+ assert(0 <= to && to <= 0x7FFFFFFF,
+ "typed array |to| index assumed int32_t");
+ assert(0 <= from && from <= 0x7FFFFFFF,
+ "typed array |from| index assumed int32_t");
+
+ // Negative counts are possible for cases like tarray.copyWithin(0, 3, 0)
+ // where |count === final - from|. As |to| is within the [0, len] range,
+ // only |final - from| may underflow; with |final| in the range [0, len]
+ // and |from| in the range [0, len] the overall subtraction range is
+ // [-len, len] for |count| -- and with |len| bounded by implementation
+ // limits to 2**31 - 1, there can be no exceeding int32_t.
+ assert(-0x7FFFFFFF - 1 <= count && count <= 0x7FFFFFFF,
+ "typed array element count assumed int32_t");
+
+ // Steps 15-17.
+ //
+ // Note that getting or setting a typed array element must throw if the
+ // typed array is neutered, so the intrinsic below checks for neutering.
+ // This happens *only* if a get/set occurs, i.e. when |count > 0|.
+ //
+ // Also note that this copies elements effectively by memmove, *not* in
+ // step 17's specified order. This is unobservable, but it would be if we
+ // used this method to implement shared typed arrays' copyWithin.
+ if (count > 0)
+ MoveTypedArrayElements(obj, to | 0, from | 0, count | 0);
+
+ // Step 18.
+ return obj;
+}
+
// ES6 draft rev30 (2014/12/24) 22.2.3.6 %TypedArray%.prototype.entries()
function TypedArrayEntries() {
// Step 1.
@@ -678,6 +749,52 @@ function TypedArraySome(callbackfn, thisArg = undefined) {
return false;
}
+// ES6 draft 20150304 %TypedArray%.prototype.subarray
+function TypedArraySubarray(begin, end) {
+ // Step 1.
+ var obj = this;
+
+ // Steps 2-3.
+ // This function is not generic.
+ if (!IsObject(obj) || !IsTypedArray(obj)) {
+ return callFunction(CallTypedArrayMethodIfWrapped, this, begin, end,
+ "TypedArraySubarray");
+ }
+
+ // Steps 4-6.
+ var buffer = TypedArrayBuffer(obj);
+ var srcLength = TypedArrayLength(obj);
+
+ // Steps 7-9.
+ var relativeBegin = ToInteger(begin);
+ var beginIndex = relativeBegin < 0 ? std_Math_max(srcLength + relativeBegin, 0)
+ : std_Math_min(relativeBegin, srcLength);
+
+ // Steps 10-12.
+ var relativeEnd = end === undefined ? srcLength : ToInteger(end);
+ var endIndex = relativeEnd < 0 ? std_Math_max(srcLength + relativeEnd, 0)
+ : std_Math_min(relativeEnd, srcLength);
+
+ // Step 13.
+ var newLength = std_Math_max(endIndex - beginIndex, 0);
+
+ // Steps 14-15, altered to use a shift instead of a size for performance.
+ var elementShift = TypedArrayElementShift(obj);
+
+ // Step 16.
+ var srcByteOffset = TypedArrayByteOffset(obj);
+
+ // Step 17.
+ var beginByteOffset = srcByteOffset + (beginIndex << elementShift);
+
+ // Steps 18-20.
+ var defaultConstructor = _ConstructorForTypedArray(obj);
+ var constructor = SpeciesConstructor(obj, defaultConstructor);
+
+ // Steps 21-22.
+ return new constructor(buffer, beginByteOffset, newLength);
+}
+
// ES6 draft rev30 (2014/12/24) 22.2.3.30 %TypedArray%.prototype.values()
function TypedArrayValues() {
// Step 1.
diff --git a/js/src/devtools/automation/autospider.sh b/js/src/devtools/automation/autospider.sh
index 3bbb8abedd5e..233519b639ee 100755
--- a/js/src/devtools/automation/autospider.sh
+++ b/js/src/devtools/automation/autospider.sh
@@ -32,7 +32,14 @@ while [ $# -gt 1 ]; do
done
VARIANT=$1
-if [ ! -f "$ABSDIR/$VARIANT" ]; then
+
+# 'generational' is being retired in favor of 'compacting', but we need to
+# decouple the landings.
+if [[ "$VARIANT" = "generational" ]]; then
+ VARIANT=compacting
+fi
+
+if [ ! -f "$ABSDIR/variants/$VARIANT" ]; then
echo "Could not find variant '$VARIANT'"
usage
exit 1
@@ -44,7 +51,7 @@ TRY_OVERRIDE=$SOURCE/js/src/config.try
if [ -r $TRY_OVERRIDE ]; then
CONFIGURE_ARGS="$(cat "$TRY_OVERRIDE")"
else
- CONFIGURE_ARGS="$(cat "$ABSDIR/$VARIANT")"
+ CONFIGURE_ARGS="$(cat "$ABSDIR/variants/$VARIANT")"
fi
OBJDIR="${OBJDIR:-$SOURCE/obj-spider}"
@@ -123,8 +130,7 @@ RUN_JSTESTS=true
if [[ "$VARIANT" = "rootanalysis" ]]; then
export JS_GC_ZEAL=7
-elif [[ "$VARIANT" = "generational" ]]; then
- # Generational is currently being used for compacting GC
+elif [[ "$VARIANT" = "compacting" ]]; then
export JS_GC_ZEAL=14
# Ignore timeouts from tests that are known to take too long with this zeal mode
diff --git a/js/src/devtools/automation/arm-sim b/js/src/devtools/automation/variants/arm-sim
similarity index 100%
rename from js/src/devtools/automation/arm-sim
rename to js/src/devtools/automation/variants/arm-sim
diff --git a/js/src/devtools/automation/variants/compacting b/js/src/devtools/automation/variants/compacting
new file mode 100644
index 000000000000..3504e523fc92
--- /dev/null
+++ b/js/src/devtools/automation/variants/compacting
@@ -0,0 +1,5 @@
+--enable-optimize
+--enable-debug
+--enable-stdcxx-compat
+--enable-ctypes
+--disable-shared-js
diff --git a/js/src/devtools/automation/dtrace b/js/src/devtools/automation/variants/dtrace
similarity index 100%
rename from js/src/devtools/automation/dtrace
rename to js/src/devtools/automation/variants/dtrace
diff --git a/js/src/devtools/automation/exactrooting b/js/src/devtools/automation/variants/exactrooting
similarity index 100%
rename from js/src/devtools/automation/exactrooting
rename to js/src/devtools/automation/variants/exactrooting
diff --git a/js/src/devtools/automation/generational b/js/src/devtools/automation/variants/generational
similarity index 100%
rename from js/src/devtools/automation/generational
rename to js/src/devtools/automation/variants/generational
diff --git a/js/src/devtools/automation/nomethodjit b/js/src/devtools/automation/variants/nomethodjit
similarity index 100%
rename from js/src/devtools/automation/nomethodjit
rename to js/src/devtools/automation/variants/nomethodjit
diff --git a/js/src/devtools/automation/vanilla b/js/src/devtools/automation/variants/plain
similarity index 54%
rename from js/src/devtools/automation/vanilla
rename to js/src/devtools/automation/variants/plain
index 46ee21ba68a8..9605b6e57848 100644
--- a/js/src/devtools/automation/vanilla
+++ b/js/src/devtools/automation/variants/plain
@@ -1,2 +1 @@
--enable-optimize
---enable-debug
diff --git a/js/src/devtools/automation/variants/plaindebug b/js/src/devtools/automation/variants/plaindebug
new file mode 100644
index 000000000000..dfc858b7c145
--- /dev/null
+++ b/js/src/devtools/automation/variants/plaindebug
@@ -0,0 +1 @@
+--enable-debug
diff --git a/js/src/devtools/automation/rootanalysis b/js/src/devtools/automation/variants/rootanalysis
similarity index 100%
rename from js/src/devtools/automation/rootanalysis
rename to js/src/devtools/automation/variants/rootanalysis
diff --git a/js/src/devtools/automation/warnaserr b/js/src/devtools/automation/variants/warnaserr
similarity index 100%
rename from js/src/devtools/automation/warnaserr
rename to js/src/devtools/automation/variants/warnaserr
diff --git a/js/src/devtools/automation/warnaserrdebug b/js/src/devtools/automation/variants/warnaserrdebug
similarity index 100%
rename from js/src/devtools/automation/warnaserrdebug
rename to js/src/devtools/automation/variants/warnaserrdebug
diff --git a/js/src/doc/SavedFrame/SavedFrame.md b/js/src/doc/SavedFrame/SavedFrame.md
index 3dcafe998a83..7d06b445b806 100644
--- a/js/src/doc/SavedFrame/SavedFrame.md
+++ b/js/src/doc/SavedFrame/SavedFrame.md
@@ -59,10 +59,27 @@ as the content compartment sees it, waive the xray wrapper with
: Either SpiderMonkey's inferred name for this stack frame's function, or
`null`.
-`parent`
-: Either this stack frame's parent stack frame (the next older frame), or
- `null` if this is the oldest frame in the captured stack.
+`asyncCause`
+: If this stack frame is the `asyncParent` of other stack frames, then this is
+ a string representing the type of asynchronous call by which this frame
+ invoked its children. For example, if this frame's children are calls to
+ handlers for a promise this frame created, this frame's `asyncCause` would
+ be `"Promise"`. If the asynchronous call was started in a descendant frame
+ to which the requester of the property does not have access, this will be
+ the generic string `"Async"`. If this is not an asynchronous call point,
+ this will be `null`.
+`asyncParent`
+: If this stack frame was called as a result of an asynchronous operation, for
+ example if the function referenced by this frame is a promise handler, this
+ property points to the stack frame responsible for the asynchronous call,
+ for example where the promise was created. If the frame responsible for the
+ call is not accessible to the caller, this points to the youngest accessible
+ ancestor of the real frame, if any. In all other cases, this is `null`.
+
+`parent`
+: This stack frame's caller, or `null` if this is the oldest frame on the
+ stack. In this case, there might be an `asyncParent` instead.
## Function Properties of the `SavedFrame.prototype` Object
diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp
index b635f10abe10..28482281ec67 100644
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -630,6 +630,11 @@ Parser::cloneParseTree(ParseNode *opn)
{
JS_CHECK_RECURSION(context, return nullptr);
+ if (opn->isKind(PNK_COMPUTED_NAME)) {
+ report(ParseError, false, opn, JSMSG_COMPUTED_NAME_IN_PATTERN);
+ return null();
+ }
+
ParseNode *pn = handler.new_(opn->getKind(), opn->getOp(), opn->getArity(),
opn->pn_pos);
if (!pn)
diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp
index 89e88f4ecb3e..fc686d4f7890 100644
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -89,20 +89,7 @@ GCIfNeeded(ExclusiveContext *cx)
}
}
- // Trigger a full, non-incremental GC if we are over the limit.
- JSRuntime *unsafeRt = cx->zone()->runtimeFromAnyThread();
- if (unsafeRt->gc.usage.gcBytes() >= unsafeRt->gc.tunables.gcMaxBytes()) {
- if (cx->isJSContext()) {
- JSRuntime *rt = cx->asJSContext()->runtime();
- JS::PrepareForFullGC(rt);
- AutoKeepAtoms keepAtoms(cx->perThreadData);
- rt->gc.gc(GC_SHRINK, JS::gcreason::LAST_DITCH);
- rt->gc.waitBackgroundSweepOrAllocEnd();
- }
- }
-
- // Check our heap constraints and fail the allocation if we are over them.
- return unsafeRt->gc.usage.gcBytes() < unsafeRt->gc.tunables.gcMaxBytes();
+ return true;
}
template
@@ -110,10 +97,8 @@ static inline bool
CheckAllocatorState(ExclusiveContext *cx, AllocKind kind)
{
if (allowGC) {
- if (!GCIfNeeded(cx)) {
- ReportOutOfMemory(cx);
+ if (!GCIfNeeded(cx))
return false;
- }
}
if (!cx->isJSContext())
diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h
index a0145ecb09a6..78930f74c172 100644
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -785,6 +785,7 @@ class GCRuntime
bool minorGCRequested() const { return minorGCTriggerReason != JS::gcreason::NO_REASON; }
bool majorGCRequested() const { return majorGCTriggerReason != JS::gcreason::NO_REASON; }
+ bool isGcNeeded() { return minorGCRequested() || majorGCRequested(); }
double computeHeapGrowthFactor(size_t lastBytes);
size_t computeTriggerBytes(double growthFactor, size_t lastBytes);
diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp
index b6dd41110c9e..3c138125c312 100644
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -451,6 +451,14 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc,
MarkPersistentRootedChains(trc);
}
+ if (rt->asyncStackForNewActivations)
+ MarkObjectRoot(trc, &rt->asyncStackForNewActivations,
+ "asyncStackForNewActivations");
+
+ if (rt->asyncCauseForNewActivations)
+ MarkStringRoot(trc, &rt->asyncCauseForNewActivations,
+ "asyncCauseForNewActivations");
+
if (rt->scriptAndCountsVector) {
ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
for (size_t i = 0; i < vec.length(); i++)
diff --git a/js/src/gdb/mozilla/Interpreter.py b/js/src/gdb/mozilla/Interpreter.py
new file mode 100644
index 000000000000..3c9ffc5fbb3f
--- /dev/null
+++ b/js/src/gdb/mozilla/Interpreter.py
@@ -0,0 +1,39 @@
+# Pretty-printers for InterpreterRegs.
+
+import gdb
+import mozilla.prettyprinters as prettyprinters
+
+prettyprinters.clear_module_printers(__name__)
+
+from mozilla.prettyprinters import pretty_printer
+
+# Cache information about the Interpreter types for this objfile.
+class InterpreterTypeCache(object):
+ def __init__(self):
+ self.tValue = gdb.lookup_type('JS::Value')
+ self.tJSOp = gdb.lookup_type('JSOp')
+
+@pretty_printer('js::InterpreterRegs')
+class InterpreterRegs(object):
+ def __init__(self, value, cache):
+ self.value = value
+ self.cache = cache
+ if not cache.mod_Interpreter:
+ cache.mod_Interpreter = InterpreterTypeCache()
+ self.itc = cache.mod_Interpreter
+
+ # There's basically no way to co-operate with 'set print pretty' (how would
+ # you get the current level of indentation?), so we don't even bother
+ # trying. No 'children', just 'to_string'.
+ def to_string(self):
+ fp_ = 'fp_ = {}'.format(self.value['fp_'])
+ slots = (self.value['fp_'] + 1).cast(self.itc.tValue.pointer())
+ sp = 'sp = fp_.slots() + {}'.format(self.value['sp'] - slots)
+ pc = self.value['pc']
+ try:
+ opcode = pc.dereference().cast(self.itc.tJSOp)
+ except:
+ opcode = 'bad pc'
+ pc = 'pc = {} ({})'.format(pc.cast(self.cache.void_ptr_t), opcode)
+ return '{{ {}, {}, {} }}'.format(fp_, sp, pc)
+
diff --git a/js/src/gdb/mozilla/JSObject.py b/js/src/gdb/mozilla/JSObject.py
index 96a720df0bbe..5efdcc8f1b7d 100644
--- a/js/src/gdb/mozilla/JSObject.py
+++ b/js/src/gdb/mozilla/JSObject.py
@@ -10,14 +10,11 @@ prettyprinters.clear_module_printers(__name__)
class JSObjectTypeCache(object):
def __init__(self, value, cache):
- # In GDB 7.4.50, with some programs, this first dummy gdb.lookup_type
- # call is required to make the second lookup_type call succeed. A GDB
- # built from the public sources as of 2012-12-12 did not require the
- # dummy lookup.
- gdb.lookup_type('js::BaseShape')
baseshape_flags = gdb.lookup_type('js::BaseShape::Flag')
self.flag_DELEGATE = prettyprinters.enum_value(baseshape_flags, 'js::BaseShape::DELEGATE')
self.func_ptr_type = gdb.lookup_type('JSFunction').pointer()
+ self.class_NON_NATIVE = gdb.parse_and_eval('js::Class::NON_NATIVE')
+ self.NativeObject_ptr_t = gdb.lookup_type('js::NativeObject').pointer()
# There should be no need to register this for JSFunction as well, since we
# search for pretty-printers under the names of base classes, and
@@ -32,24 +29,30 @@ class JSObjectPtrOrRef(prettyprinters.Pointer):
self.otc = cache.mod_JSObject
def summary(self):
- shape = deref(self.value['shape_'])
- baseshape = deref(shape['base_'])
group = deref(self.value['group_'])
- class_name = group['clasp_']['name'].string()
- flags = baseshape['flags']
- is_delegate = bool(flags & self.otc.flag_DELEGATE)
- name = None
- if class_name == 'Function':
- function = self.value
- concrete_type = function.type.strip_typedefs()
- if concrete_type.code == gdb.TYPE_CODE_REF:
- function = function.address
- function = function.cast(self.otc.func_ptr_type)
- atom = deref(function['atom_'])
- name = str(atom) if atom else ''
- return '[object %s%s]%s' % (class_name,
- ' ' + name if name else '',
- ' delegate' if is_delegate else '')
+ classp = group['clasp_']
+ class_name = classp['name'].string()
+ non_native = classp['flags'] & self.otc.class_NON_NATIVE
+ if non_native:
+ return '[object {}]'.format(class_name)
+ else:
+ native = self.value.cast(self.otc.NativeObject_ptr_t)
+ shape = deref(native['shape_'])
+ baseshape = deref(shape['base_'])
+ flags = baseshape['flags']
+ is_delegate = bool(flags & self.otc.flag_DELEGATE)
+ name = None
+ if class_name == 'Function':
+ function = self.value
+ concrete_type = function.type.strip_typedefs()
+ if concrete_type.code == gdb.TYPE_CODE_REF:
+ function = function.address
+ function = function.cast(self.otc.func_ptr_type)
+ atom = deref(function['atom_'])
+ name = str(atom) if atom else ''
+ return '[object {}{}]{}'.format(class_name,
+ ' ' + name if name else '',
+ ' delegate' if is_delegate else '')
@ref_pretty_printer('JSObject')
def JSObjectRef(value, cache): return JSObjectPtrOrRef(value, cache)
diff --git a/js/src/gdb/mozilla/autoload.py b/js/src/gdb/mozilla/autoload.py
index 5e577a675b25..294bbfd0c368 100644
--- a/js/src/gdb/mozilla/autoload.py
+++ b/js/src/gdb/mozilla/autoload.py
@@ -8,6 +8,7 @@ import mozilla.prettyprinters
# Import the pretty-printer modules. As a side effect, loading these
# modules registers their printers with mozilla.prettyprinters.
+import mozilla.Interpreter
import mozilla.jsid
import mozilla.JSObject
import mozilla.JSString
diff --git a/js/src/gdb/mozilla/prettyprinters.py b/js/src/gdb/mozilla/prettyprinters.py
index 6da8f38e5c01..6cdd0b81b48f 100644
--- a/js/src/gdb/mozilla/prettyprinters.py
+++ b/js/src/gdb/mozilla/prettyprinters.py
@@ -182,6 +182,7 @@ class TypeCache(object):
self.mod_JSString = None
self.mod_JSObject = None
self.mod_jsval = None
+ self.mod_Interpreter = None
# Yield a series of all the types that |t| implements, by following typedefs
# and iterating over base classes. Specifically:
diff --git a/js/src/jit-test/tests/SIMD/inline-missing-arguments.js b/js/src/jit-test/tests/SIMD/inline-missing-arguments.js
new file mode 100644
index 000000000000..4c0e3b381267
--- /dev/null
+++ b/js/src/jit-test/tests/SIMD/inline-missing-arguments.js
@@ -0,0 +1,23 @@
+load(libdir + 'simd.js');
+
+setJitCompilerOption("ion.warmup.trigger", 50);
+
+function test(i) {
+ assertEqX4(SIMD.int32x4(), [0, 0, 0, 0]);
+ assertEqX4(SIMD.int32x4(i), [i, 0, 0, 0]);
+ assertEqX4(SIMD.int32x4(i, 1), [i, 1, 0, 0]);
+ assertEqX4(SIMD.int32x4(i, 1, 2), [i, 1, 2, 0]);
+ assertEqX4(SIMD.int32x4(i, 1, 2, 3), [i, 1, 2, 3]);
+ assertEqX4(SIMD.int32x4(i, 1, 2, 3, 4), [i, 1, 2, 3]);
+
+ assertEqX4(SIMD.float32x4(), [NaN, NaN, NaN, NaN]);
+ assertEqX4(SIMD.float32x4(i), [i, NaN, NaN, NaN]);
+ assertEqX4(SIMD.float32x4(i, 1), [i, 1, NaN, NaN]);
+ assertEqX4(SIMD.float32x4(i, 1, 2), [i, 1, 2, NaN]);
+ assertEqX4(SIMD.float32x4(i, 1, 2, 3), [i, 1, 2, 3 ]);
+ assertEqX4(SIMD.float32x4(i, 1, 2, 3, 4), [i, 1, 2, 3 ]);
+}
+
+for(var i=0; i<300; i++) {
+ test(i);
+}
diff --git a/js/src/jit-test/tests/basic/typed-array-copyWithin.js b/js/src/jit-test/tests/basic/typed-array-copyWithin.js
index c5c9421d46ae..1449ca1ae0ff 100644
--- a/js/src/jit-test/tests/basic/typed-array-copyWithin.js
+++ b/js/src/jit-test/tests/basic/typed-array-copyWithin.js
@@ -172,6 +172,28 @@ for (var constructor of constructors) {
assertEq(e, 42, "should have failed converting target to index");
}
+ function neuterAndConvertTo(x) {
+ return { valueOf() { neuter(tarray.buffer, "change-data"); return x; } };
+ }
+
+ // Neutering during argument processing triggers a TypeError.
+ tarray = new constructor([1, 2, 3, 4, 5]);
+ try
+ {
+ tarray.copyWithin(0, 3, neuterAndConvertTo(4));
+ throw new Error("expected to throw");
+ }
+ catch (e)
+ {
+ assertEq(e instanceof TypeError, true,
+ "expected throw with neutered array during set");
+ }
+
+ // ...unless no elements are to be copied.
+ tarray = new constructor([1, 2, 3, 4, 5]);
+ assertDeepEq(tarray.copyWithin(0, 3, neuterAndConvertTo(3)),
+ new constructor([]));
+
/* // fails, unclear whether it should, disabling for now
// test with a proxy object
var handler = {
diff --git a/js/src/jit-test/tests/debug/bug1109328.js b/js/src/jit-test/tests/debug/bug1109328.js
index 34f1d514e465..f369bde66b8a 100644
--- a/js/src/jit-test/tests/debug/bug1109328.js
+++ b/js/src/jit-test/tests/debug/bug1109328.js
@@ -1,4 +1,3 @@
-// |jit-test| allow-oom
try {
gcslice(0)(""());
} catch (e) {}
diff --git a/js/src/jit-test/tests/gc/bug-957110.js b/js/src/jit-test/tests/gc/bug-957110.js
index 1f44cbb13a8e..0a0ce489a799 100644
--- a/js/src/jit-test/tests/gc/bug-957110.js
+++ b/js/src/jit-test/tests/gc/bug-957110.js
@@ -1,4 +1,3 @@
-// |jit-test| allow-oom
gczeal(7,1);
try {
gcparam("maxBytes", gcparam("gcBytes") + 4*1024);
diff --git a/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js b/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js
new file mode 100644
index 000000000000..3a9ceb9ea6d0
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/async-max-frame-count.js
@@ -0,0 +1,84 @@
+// Test that async stacks are limited on recursion.
+
+const defaultAsyncStackLimit = 60;
+
+function recur(n, limit) {
+ if (n > 0) {
+ return callFunctionWithAsyncStack(recur.bind(undefined, n - 1, limit),
+ saveStack(limit), "Recurse");
+ }
+ return saveStack(limit);
+}
+
+function checkRecursion(n, limit) {
+ print("checkRecursion(" + uneval(n) + ", " + uneval(limit) + ")");
+
+ var stack = recur(n, limit);
+
+ // Async stacks are limited even if we didn't ask for a limit. There is a
+ // default limit on frames attached on top of any synchronous frames. In this
+ // case the synchronous frame is the last call to `recur`.
+ if (limit == 0) {
+ limit = defaultAsyncStackLimit + 1;
+ }
+
+ // The first `n` or `limit` frames should have `recur` as their `asyncParent`.
+ for (var i = 0; i < Math.min(n, limit); i++) {
+ assertEq(stack.functionDisplayName, "recur");
+ assertEq(stack.parent, null);
+ stack = stack.asyncParent;
+ }
+
+ // This frame should be the first call to `recur`.
+ if (limit > n) {
+ assertEq(stack.functionDisplayName, "recur");
+ assertEq(stack.asyncParent, null);
+ stack = stack.parent;
+ } else {
+ assertEq(stack, null);
+ }
+
+ // This frame should be the call to `checkRecursion`.
+ if (limit > n + 1) {
+ assertEq(stack.functionDisplayName, "checkRecursion");
+ assertEq(stack.asyncParent, null);
+ stack = stack.parent;
+ } else {
+ assertEq(stack, null);
+ }
+
+ // We should be at the top frame, which is the test script itself.
+ if (limit > n + 2) {
+ assertEq(stack.functionDisplayName, null);
+ assertEq(stack.asyncParent, null);
+ assertEq(stack.parent, null);
+ } else {
+ assertEq(stack, null);
+ }
+}
+
+// Check capturing with no limit, which should still apply a default limit.
+checkRecursion(0, 0);
+checkRecursion(1, 0);
+checkRecursion(2, 0);
+checkRecursion(defaultAsyncStackLimit + 10, 0);
+
+// Limit of 1 frame.
+checkRecursion(0, 1);
+checkRecursion(1, 1);
+checkRecursion(2, 1);
+
+// Limit of 2 frames.
+checkRecursion(0, 2);
+checkRecursion(1, 2);
+checkRecursion(2, 2);
+
+// Limit of 3 frames.
+checkRecursion(0, 3);
+checkRecursion(1, 3);
+checkRecursion(2, 3);
+
+// Limit higher than the default limit.
+checkRecursion(defaultAsyncStackLimit + 10, defaultAsyncStackLimit + 10);
+checkRecursion(defaultAsyncStackLimit + 11, defaultAsyncStackLimit + 10);
+checkRecursion(defaultAsyncStackLimit + 12, defaultAsyncStackLimit + 10);
diff --git a/js/src/jit-test/tests/saved-stacks/async-principals.js b/js/src/jit-test/tests/saved-stacks/async-principals.js
new file mode 100644
index 000000000000..75eced08de13
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/async-principals.js
@@ -0,0 +1,244 @@
+// Test cases when a SavedFrame or one of its ancestors have a principal that is
+// not subsumed by the caller's principal, when async parents are present.
+
+function checkVisibleStack(stackFrame, expectedFrames) {
+ // We check toString separately from properties like asyncCause to check that
+ // it walks the physical SavedFrame chain consistently with the properties.
+ var stringFrames = stackFrame.toString().split("\n");
+
+ while (expectedFrames.length) {
+ let expectedFrame = expectedFrames.shift();
+ let stringFrame = stringFrames.shift();
+
+ // Check the frame properties.
+ assertEq(stackFrame.functionDisplayName, expectedFrame.name);
+ assertEq(stackFrame.asyncCause, expectedFrame.asyncCause);
+
+ // Check the stringified version.
+ let expectedStart =
+ (expectedFrame.asyncCause ? expectedFrame.asyncCause + "*" : "") +
+ expectedFrame.name;
+ assertEq(stringFrame.replace(/@.*$/, ""), expectedStart);
+
+ // If the next frame has an asyncCause, it should be an asyncParent.
+ if (expectedFrames.length && expectedFrames[0].asyncCause) {
+ assertEq(stackFrame.parent, null);
+ stackFrame = stackFrame.asyncParent;
+ } else {
+ assertEq(stackFrame.asyncParent, null);
+ stackFrame = stackFrame.parent;
+ }
+ }
+}
+
+var low = newGlobal({ principal: 0 });
+var high = newGlobal({ principal: 0xfffff });
+
+// Test with synchronous cross-compartment calls.
+//
+// With arrows representing child-to-parent links, and fat arrows representing
+// child-to-async-parent links, create a SavedFrame stack like this:
+//
+// low.e -> high.d => high.c => high.b -> low.a
+//
+// This stack captured in function `e` would be seen in its complete version if
+// accessed by `high`'s compartment, while in `low`'s compartment it would look
+// like this:
+//
+// low.e => low.a
+//
+// The asyncCause seen on `low.a` above should not leak information about the
+// real asyncCause on `high.c` and `high.d`.
+//
+// The stack captured in function `d` would be seen in its complete version if
+// accessed by `high`'s compartment, while in `low`'s compartment it would look
+// like this:
+//
+// low.a
+
+// We'll move these functions into the right globals below before invoking them.
+function a() {
+ b();
+}
+function b() {
+ callFunctionWithAsyncStack(c, saveStack(), "BtoC");
+}
+function c() {
+ callFunctionWithAsyncStack(d, saveStack(), "CtoD");
+}
+function d() {
+ let stackD = saveStack();
+
+ print("high.checkVisibleStack(stackD)");
+ checkVisibleStack(stackD, [
+ { name: "d", asyncCause: null },
+ { name: "c", asyncCause: "CtoD" },
+ { name: "b", asyncCause: "BtoC" },
+ { name: "a", asyncCause: null },
+ ]);
+
+ let stackE = e(saveStack(0, low));
+
+ print("high.checkVisibleStack(stackE)");
+ checkVisibleStack(stackE, [
+ { name: "e", asyncCause: null },
+ { name: "d", asyncCause: null },
+ { name: "c", asyncCause: "CtoD" },
+ { name: "b", asyncCause: "BtoC" },
+ { name: "a", asyncCause: null },
+ ]);
+}
+function e(stackD) {
+ print("low.checkVisibleStack(stackD)");
+ checkVisibleStack(stackD, [
+ { name: "a", asyncCause: "Async" },
+ ]);
+
+ let stackE = saveStack();
+
+ print("low.checkVisibleStack(stackE)");
+ checkVisibleStack(stackE, [
+ { name: "e", asyncCause: null },
+ { name: "a", asyncCause: "Async" },
+ ]);
+
+ return saveStack(0, high);
+}
+
+// Test with asynchronous cross-compartment calls and shared frames.
+//
+// With arrows representing child-to-parent links, and fat arrows representing
+// child-to-async-parent links, create a SavedFrame stack like this:
+//
+// low.x => high.v => low.u
+// low.y -> high.v => low.u
+// low.z => high.w -> low.u
+//
+// This stack captured in functions `x`, `y`, and `z` would be seen in its
+// complete version if accessed by `high`'s compartment, while in `low`'s
+// compartment it would look like this:
+//
+// low.x => low.u
+// low.y => low.u
+// low.z => low.u
+//
+// The stack captured in function `v` would be seen in its complete version if
+// accessed by `high`'s compartment, while in `low`'s compartment it would look
+// like this:
+//
+// low.u
+
+// We'll move these functions into the right globals below before invoking them.
+function u() {
+ callFunctionWithAsyncStack(v, saveStack(), "UtoV");
+ w();
+}
+function v() {
+ let stackV = saveStack();
+ print("high.checkVisibleStack(stackV)");
+ checkVisibleStack(stackV, [
+ { name: "v", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ let xToCall = x.bind(undefined, saveStack(0, low));
+
+ let stackX = callFunctionWithAsyncStack(xToCall, saveStack(), "VtoX");
+
+ print("high.checkVisibleStack(stackX)");
+ checkVisibleStack(stackX, [
+ { name: "x", asyncCause: null },
+ { name: "v", asyncCause: "VtoX" },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ let stackY = y();
+
+ print("high.checkVisibleStack(stackY)");
+ checkVisibleStack(stackY, [
+ { name: "y", asyncCause: null },
+ { name: "v", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+}
+function w() {
+ let stackZ = callFunctionWithAsyncStack(z, saveStack(), "WtoZ");
+
+ print("high.checkVisibleStack(stackZ)");
+ checkVisibleStack(stackZ, [
+ { name: "z", asyncCause: null },
+ { name: "w", asyncCause: "WtoZ" },
+ { name: "u", asyncCause: null },
+ ]);
+}
+function x(stackV) {
+ print("low.checkVisibleStack(stackV)");
+ checkVisibleStack(stackV, [
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ let stackX = saveStack();
+
+ print("low.checkVisibleStack(stackX)");
+ checkVisibleStack(stackX, [
+ { name: "x", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ return saveStack(0, high);
+}
+function y() {
+ let stackY = saveStack();
+
+ print("low.checkVisibleStack(stackY)");
+ checkVisibleStack(stackY, [
+ { name: "y", asyncCause: null },
+ { name: "u", asyncCause: "UtoV" },
+ ]);
+
+ return saveStack(0, high);
+}
+function z() {
+ let stackZ = saveStack();
+
+ print("low.checkVisibleStack(stackZ)");
+ checkVisibleStack(stackZ, [
+ { name: "z", asyncCause: null },
+ { name: "u", asyncCause: "Async" },
+ ]);
+
+ return saveStack(0, high);
+}
+
+// Split the functions in their respective globals.
+low .eval(a.toSource());
+high.eval(b.toSource());
+high.eval(c.toSource());
+high.eval(d.toSource());
+low .eval(e.toSource());
+
+low .b = high.b;
+high.e = low .e;
+
+low .eval(u.toSource());
+high.eval(v.toSource());
+high.eval(w.toSource());
+low .eval(x.toSource());
+low .eval(y.toSource());
+low .eval(z.toSource());
+
+low .v = high.v;
+low .w = high.w;
+high.x = low .x;
+high.y = low .y;
+high.z = low .z;
+
+low .high = high;
+high.low = low;
+
+low .eval(checkVisibleStack.toSource());
+high.eval(checkVisibleStack.toSource());
+
+// Execute the tests.
+low.a();
+low.u();
diff --git a/js/src/jit-test/tests/saved-stacks/async.js b/js/src/jit-test/tests/saved-stacks/async.js
new file mode 100644
index 000000000000..6ab4546a7c7a
--- /dev/null
+++ b/js/src/jit-test/tests/saved-stacks/async.js
@@ -0,0 +1,24 @@
+// Test calling a function using a previously captured stack as an async stack.
+
+function getAsyncStack() {
+ return saveStack();
+}
+
+// asyncCause may contain non-ASCII characters.
+let testAsyncCause = "Tes" + String.fromCharCode(355) + "String";
+
+callFunctionWithAsyncStack(function asyncCallback() {
+ let stack = saveStack();
+
+ assertEq(stack.functionDisplayName, "asyncCallback");
+ assertEq(stack.parent, null);
+ assertEq(stack.asyncCause, null);
+
+ assertEq(stack.asyncParent.functionDisplayName, "getAsyncStack");
+ assertEq(stack.asyncParent.asyncCause, testAsyncCause);
+ assertEq(stack.asyncParent.asyncParent, null);
+
+ assertEq(stack.asyncParent.parent.asyncCause, null);
+ assertEq(stack.asyncParent.parent.asyncParent, null);
+ assertEq(stack.asyncParent.parent.parent, null);
+}, getAsyncStack(), testAsyncCause);
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index ef2461b5db88..ac5a024525d5 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -1288,6 +1288,13 @@ class CallInfo
return args_[i];
}
+ MDefinition *getArgWithDefault(uint32_t i, MDefinition *defaultValue) const {
+ if (i < argc())
+ return args_[i];
+
+ return defaultValue;
+ }
+
void setArg(uint32_t i, MDefinition *def) {
MOZ_ASSERT(i < argc());
args_[i] = def;
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp
index aafb0cf6870c..80406b107f9a 100644
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -24,8 +24,9 @@
using mozilla::ArrayLength;
-using JS::TrackedStrategy;
+using JS::DoubleNaNValue;
using JS::TrackedOutcome;
+using JS::TrackedStrategy;
using JS::TrackedTypeSite;
namespace js {
@@ -2238,7 +2239,6 @@ IonBuilder::inlineTypedArrayLength(CallInfo &callInfo)
return InliningStatus_Inlined;
}
-
IonBuilder::InliningStatus
IonBuilder::inlineObjectIsTypeDescr(CallInfo &callInfo)
{
@@ -2909,9 +2909,6 @@ IonBuilder::inlineConstructTypedObject(CallInfo &callInfo, TypeDescr *descr)
IonBuilder::InliningStatus
IonBuilder::inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *descr)
{
- if (callInfo.argc() == 1)
- return InliningStatus_NotInlined;
-
// Generic constructor of SIMD valuesX4.
MIRType simdType = SimdTypeDescrToMIRType(descr->type());
@@ -2919,11 +2916,6 @@ IonBuilder::inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *descr)
if (simdType == MIRType_Undefined)
return InliningStatus_NotInlined;
- // We do not inline SIMD constructors if the number of arguments does not
- // match the number of lanes.
- if (SimdTypeToLength(simdType) != callInfo.argc())
- return InliningStatus_NotInlined;
-
// Take the templateObject out of Baseline ICs, such that we can box
// SIMD value type in the same kind of objects.
MOZ_ASSERT(size_t(descr->size(descr->type())) < InlineTypedObject::MaximumSize);
@@ -2936,9 +2928,24 @@ IonBuilder::inlineConstructSimdObject(CallInfo &callInfo, SimdTypeDescr *descr)
InlineTypedObject *inlineTypedObject = &templateObject->as();
MOZ_ASSERT(&inlineTypedObject->typeDescr() == descr);
- MSimdValueX4 *values = MSimdValueX4::New(alloc(), simdType,
- callInfo.getArg(0), callInfo.getArg(1),
- callInfo.getArg(2), callInfo.getArg(3));
+ // When there are missing arguments, provide a default value
+ // containing the coercion of 'undefined' to the right type.
+ MConstant *defVal = nullptr;
+ if (callInfo.argc() < SimdTypeToLength(simdType)) {
+ MIRType scalarType = SimdTypeToScalarType(simdType);
+ if (scalarType == MIRType_Int32) {
+ defVal = constant(Int32Value(0));
+ } else {
+ MOZ_ASSERT(IsFloatingPointType(scalarType));
+ defVal = constant(DoubleNaNValue());
+ defVal->setResultType(scalarType);
+ }
+ }
+
+ MSimdValueX4 *values =
+ MSimdValueX4::New(alloc(), simdType,
+ callInfo.getArgWithDefault(0, defVal), callInfo.getArgWithDefault(1, defVal),
+ callInfo.getArgWithDefault(2, defVal), callInfo.getArgWithDefault(3, defVal));
current->add(values);
MSimdBox *obj = MSimdBox::New(alloc(), constraints(), values, inlineTypedObject,
diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h
index 6e989f37733b..a241f3f7cfff 100644
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -252,11 +252,6 @@ struct VMFunction
}
VMFunction(const VMFunction &o) {
- init(o);
- }
-
- void init(const VMFunction &o) {
- MOZ_ASSERT(!wrapped);
*this = o;
addToFunctions();
}
diff --git a/js/src/js.msg b/js/src/js.msg
index 79826514d982..adf984eb085a 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -325,6 +325,7 @@ MSG_DEF(JSMSG_WHILE_AFTER_DO, 0, JSEXN_SYNTAXERR, "missing while after
MSG_DEF(JSMSG_YIELD_IN_ARROW, 0, JSEXN_SYNTAXERR, "arrow function may not contain yield")
MSG_DEF(JSMSG_YIELD_IN_DEFAULT, 0, JSEXN_SYNTAXERR, "yield in default expression")
MSG_DEF(JSMSG_BAD_COLUMN_NUMBER, 0, JSEXN_RANGEERR, "column number out of range")
+MSG_DEF(JSMSG_COMPUTED_NAME_IN_PATTERN,0, JSEXN_SYNTAXERR, "computed property names aren't supported in this destructuring declaration")
// asm.js
MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}")
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 5a80d4f645bb..e351f8cbcdf7 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4318,6 +4318,28 @@ JS_RestoreFrameChain(JSContext *cx)
cx->restoreFrameChain();
}
+JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
+ JSContext *cx, HandleObject stack, HandleString asyncCause)
+ : cx(cx),
+ oldAsyncStack(cx, cx->runtime()->asyncStackForNewActivations),
+ oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations)
+{
+ CHECK_REQUEST(cx);
+
+ SavedFrame *asyncStack = &stack->as();
+ MOZ_ASSERT(!asyncCause->empty());
+
+ cx->runtime()->asyncStackForNewActivations = asyncStack;
+ cx->runtime()->asyncCauseForNewActivations = asyncCause;
+}
+
+JS::AutoSetAsyncStackForNewCalls::~AutoSetAsyncStackForNewCalls()
+{
+ cx->runtime()->asyncCauseForNewActivations = oldAsyncCause;
+ cx->runtime()->asyncStackForNewActivations =
+ oldAsyncStack ? &oldAsyncStack->as() : nullptr;
+}
+
/************************************************************************/
JS_PUBLIC_API(JSString *)
JS_NewStringCopyN(JSContext *cx, const char *s, size_t n)
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 0dddad77eb77..c310894e5ae2 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3811,6 +3811,40 @@ JS_SaveFrameChain(JSContext *cx);
extern JS_PUBLIC_API(void)
JS_RestoreFrameChain(JSContext *cx);
+namespace JS {
+
+/*
+ * This class can be used to store a pointer to the youngest frame of a saved
+ * stack in the specified JSContext. This reference will be picked up by any new
+ * calls performed until the class is destroyed, with the specified asyncCause,
+ * that must not be empty.
+ *
+ * Any stack capture initiated during these new calls will go through the async
+ * stack instead of the current stack.
+ *
+ * Capturing the stack before a new call is performed will not be affected.
+ *
+ * The provided chain of SavedFrame objects can live in any compartment,
+ * although it will be copied to the compartment where the stack is captured.
+ */
+class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
+{
+ JSContext *cx;
+ RootedObject oldAsyncStack;
+ RootedString oldAsyncCause;
+
+ public:
+ // The stack parameter cannot be null by design, because it would be
+ // ambiguous whether that would clear any scheduled async stack and make the
+ // normal stack reappear in the new call, or just keep the async stack
+ // already scheduled for the new call, if any.
+ AutoSetAsyncStackForNewCalls(JSContext *cx, HandleObject stack,
+ HandleString asyncCause);
+ ~AutoSetAsyncStackForNewCalls();
+};
+
+}
+
/************************************************************************/
/*
@@ -5105,6 +5139,20 @@ GetSavedFrameColumn(JSContext *cx, HandleObject savedFrame, uint32_t *columnp);
extern JS_PUBLIC_API(SavedFrameResult)
GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, MutableHandleString namep);
+/*
+ * Given a SavedFrame JSObject, get its asyncCause string. Defaults to nullptr.
+ */
+extern JS_PUBLIC_API(SavedFrameResult)
+GetSavedFrameAsyncCause(JSContext *cx, HandleObject savedFrame, MutableHandleString asyncCausep);
+
+/*
+ * Given a SavedFrame JSObject, get its asyncParent SavedFrame object or nullptr
+ * if there is no asyncParent. The `asyncParentp` out parameter is _NOT_
+ * guaranteed to be in the cx's compartment. Defaults to nullptr.
+ */
+extern JS_PUBLIC_API(SavedFrameResult)
+GetSavedFrameAsyncParent(JSContext *cx, HandleObject savedFrame, MutableHandleObject asyncParentp);
+
/*
* Given a SavedFrame JSObject, get its parent SavedFrame object or nullptr if
* it is the oldest frame in the stack. The `parentp` out parameter is _NOT_
diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h
index 8a573fdab105..cba54533c0b5 100644
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -840,8 +840,13 @@ bool intrinsic_IsArrayIterator(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_IsStringIterator(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_IsTypedArray(JSContext *cx, unsigned argc, Value *vp);
+bool intrinsic_TypedArrayBuffer(JSContext *cx, unsigned argc, Value *vp);
+bool intrinsic_TypedArrayByteOffset(JSContext *cx, unsigned argc, Value *vp);
+bool intrinsic_TypedArrayElementShift(JSContext *cx, unsigned argc, Value *vp);
bool intrinsic_TypedArrayLength(JSContext *cx, unsigned argc, Value *vp);
+bool intrinsic_MoveTypedArrayElements(JSContext *cx, unsigned argc, Value *vp);
+
class AutoLockForExclusiveAccess
{
JSRuntime *runtime;
diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp
index 490d2d4247b6..99a43c688e34 100644
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1038,6 +1038,14 @@ GCRuntime::allocateArena(Chunk *chunk, Zone *zone, AllocKind thingKind, const Au
{
MOZ_ASSERT(chunk->hasAvailableArenas());
+ // Fail the allocation if we are over our heap size limits.
+ if (!isHeapMinorCollecting() &&
+ !isHeapCompacting() &&
+ usage.gcBytes() >= tunables.gcMaxBytes())
+ {
+ return nullptr;
+ }
+
ArenaHeader *aheader = chunk->allocateArena(rt, zone, thingKind, lock);
zone->usage.addGCArena();
@@ -1962,17 +1970,9 @@ ArenaList::removeRemainingArenas(ArenaHeader **arenap)
}
static bool
-ShouldRelocateAllArenas(JSRuntime *runtime)
+ShouldRelocateAllArenas(JS::gcreason::Reason reason)
{
- // In compacting zeal mode and in debug builds on 64 bit architectures, we
- // relocate all arenas. The purpose of this is to balance test coverage of
- // object moving with test coverage of the arena selection routine in
- // pickArenasToRelocate().
-#if defined(DEBUG) && defined(JS_PUNBOX64)
- return true;
-#else
- return runtime->gc.zeal() == ZealCompactValue;
-#endif
+ return reason == JS::gcreason::DEBUG_GC;
}
/*
@@ -2179,7 +2179,7 @@ ArenaLists::relocateArenas(ArenaHeader *&relocatedListOut, JS::gcreason::Reason
purge();
checkEmptyFreeLists();
- if (ShouldRelocateAllArenas(runtime_)) {
+ if (ShouldRelocateAllArenas(reason)) {
for (size_t i = 0; i < FINALIZE_LIMIT; i++) {
if (CanRelocateAllocKind(AllocKind(i))) {
ArenaList &al = arenaLists[i];
@@ -5523,7 +5523,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason)
#ifndef DEBUG
releaseRelocatedArenas(relocatedList);
#else
- if (reason == JS::gcreason::DESTROY_RUNTIME || reason == JS::gcreason::LAST_DITCH) {
+ if (reason != JS::gcreason::DEBUG_GC) {
releaseRelocatedArenas(relocatedList);
} else {
MOZ_ASSERT(!relocatedArenasToRelease);
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index d26b5cd18fa9..c7e95fe17e88 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -21,6 +21,7 @@
macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
macro(ArrayType, ArrayType, "ArrayType") \
macro(ArrayValues, ArrayValues, "ArrayValues") \
+ macro(Async, Async, "Async") \
macro(buffer, buffer, "buffer") \
macro(builder, builder, "builder") \
macro(byteLength, byteLength, "byteLength") \
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index ba4fc8831ec1..bc640a97fa9b 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -123,6 +123,8 @@ JSRuntime::JSRuntime(JSRuntime *parentRuntime)
profilerSampleBufferGen_(0),
profilerSampleBufferLapCount_(1),
asmJSActivationStack_(nullptr),
+ asyncStackForNewActivations(nullptr),
+ asyncCauseForNewActivations(nullptr),
parentRuntime(parentRuntime),
interrupt_(false),
telemetryCallback(nullptr),
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index bc0548701d0d..5927ebdae155 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -660,6 +660,22 @@ struct JSRuntime : public JS::shadow::Runtime,
js::AsmJSActivation * volatile asmJSActivationStack_;
public:
+ /*
+ * Youngest frame of a saved stack that will be picked up as an async stack
+ * by any new Activation, and is nullptr when no async stack should be used.
+ *
+ * The JS::AutoSetAsyncStackForNewCalls class can be used to set this.
+ *
+ * New activations will reset this to nullptr on construction after getting
+ * the current value, and will restore the previous value on destruction.
+ */
+ js::SavedFrame *asyncStackForNewActivations;
+
+ /*
+ * Value of asyncCause to be attached to asyncStackForNewActivations.
+ */
+ JSString *asyncCauseForNewActivations;
+
js::Activation *const *addressOfActivation() const {
return &activation_;
}
diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp
index e5e9cf1de577..8fbc165d9eef 100644
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -38,23 +38,43 @@ using mozilla::Maybe;
namespace js {
+/**
+ * Maximum number of saved frames returned for an async stack.
+ */
+const unsigned ASYNC_STACK_MAX_FRAME_COUNT = 60;
+
struct SavedFrame::Lookup {
- Lookup(JSAtom *source, uint32_t line, uint32_t column, JSAtom *functionDisplayName,
- SavedFrame *parent, JSPrincipals *principals)
+ Lookup(JSAtom *source, uint32_t line, uint32_t column,
+ JSAtom *functionDisplayName, JSAtom *asyncCause, SavedFrame *parent,
+ JSPrincipals *principals)
: source(source),
line(line),
column(column),
functionDisplayName(functionDisplayName),
+ asyncCause(asyncCause),
parent(parent),
principals(principals)
{
MOZ_ASSERT(source);
}
+ explicit Lookup(SavedFrame &savedFrame)
+ : source(savedFrame.getSource()),
+ line(savedFrame.getLine()),
+ column(savedFrame.getColumn()),
+ functionDisplayName(savedFrame.getFunctionDisplayName()),
+ asyncCause(savedFrame.getAsyncCause()),
+ parent(savedFrame.getParent()),
+ principals(savedFrame.getPrincipals())
+ {
+ MOZ_ASSERT(source);
+ }
+
JSAtom *source;
uint32_t line;
uint32_t column;
JSAtom *functionDisplayName;
+ JSAtom *asyncCause;
SavedFrame *parent;
JSPrincipals *principals;
@@ -64,6 +84,10 @@ struct SavedFrame::Lookup {
gc::MarkStringUnbarriered(trc, &functionDisplayName,
"SavedFrame::Lookup::functionDisplayName");
}
+ if (asyncCause) {
+ gc::MarkStringUnbarriered(trc, &asyncCause,
+ "SavedFrame::Lookup::asyncCause");
+ }
if (parent) {
gc::MarkObjectUnbarriered(trc, &parent,
"SavedFrame::Lookup::parent");
@@ -102,6 +126,7 @@ SavedFrame::HashPolicy::hash(const Lookup &lookup)
lookup.column,
lookup.source,
lookup.functionDisplayName,
+ lookup.asyncCause,
SavedFramePtrHasher::hash(lookup.parent),
JSPrincipalsPtrHasher::hash(lookup.principals));
}
@@ -129,6 +154,10 @@ SavedFrame::HashPolicy::match(SavedFrame *existing, const Lookup &lookup)
if (functionDisplayName != lookup.functionDisplayName)
return false;
+ JSAtom *asyncCause = existing->getAsyncCause();
+ if (asyncCause != lookup.asyncCause)
+ return false;
+
return true;
}
@@ -197,6 +226,8 @@ SavedFrame::protoAccessors[] = {
JS_PSG("line", SavedFrame::lineProperty, 0),
JS_PSG("column", SavedFrame::columnProperty, 0),
JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0),
+ JS_PSG("asyncCause", SavedFrame::asyncCauseProperty, 0),
+ JS_PSG("asyncParent", SavedFrame::asyncParentProperty, 0),
JS_PSG("parent", SavedFrame::parentProperty, 0),
JS_PS_END
};
@@ -243,6 +274,16 @@ SavedFrame::getFunctionDisplayName()
return &s->asAtom();
}
+JSAtom *
+SavedFrame::getAsyncCause()
+{
+ const Value &v = getReservedSlot(JSSLOT_ASYNCCAUSE);
+ if (v.isNull())
+ return nullptr;
+ JSString *s = v.toString();
+ return &s->asAtom();
+}
+
SavedFrame *
SavedFrame::getParent()
{
@@ -272,6 +313,10 @@ SavedFrame::initFromLookup(SavedFrame::HandleLookup lookup)
lookup->functionDisplayName
? StringValue(lookup->functionDisplayName)
: NullValue());
+ setReservedSlot(JSSLOT_ASYNCCAUSE,
+ lookup->asyncCause
+ ? StringValue(lookup->asyncCause)
+ : NullValue());
setReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(lookup->parent));
setReservedSlot(JSSLOT_PRIVATE_PARENT, PrivateValue(lookup->parent));
@@ -312,10 +357,14 @@ SavedFrame::construct(JSContext *cx, unsigned argc, Value *vp)
// Return the first SavedFrame in the chain that starts with |frame| whose
// principals are subsumed by |principals|, according to |subsumes|. If there is
-// no such frame, return nullptr.
+// no such frame, return nullptr. |skippedAsync| is set to true if any of the
+// skipped frames had the |asyncCause| property set, otherwise it is explicitly
+// set to false.
static SavedFrame *
-GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame)
+GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame, bool &skippedAsync)
{
+ skippedAsync = false;
+
JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
if (!subsumes)
return frame;
@@ -323,8 +372,11 @@ GetFirstSubsumedFrame(JSContext *cx, HandleSavedFrame frame)
JSPrincipals *principals = cx->compartment()->principals;
RootedSavedFrame rootedFrame(cx, frame);
- while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals()))
+ while (rootedFrame && !subsumes(principals, rootedFrame->getPrincipals())) {
+ if (rootedFrame->getAsyncCause())
+ skippedAsync = true;
rootedFrame = rootedFrame->getParent();
+ }
return rootedFrame;
}
@@ -334,8 +386,9 @@ GetFirstSubsumedSavedFrame(JSContext *cx, HandleObject savedFrame)
{
if (!savedFrame)
return nullptr;
+ bool skippedAsync;
RootedSavedFrame frame(cx, &savedFrame->as());
- return GetFirstSubsumedFrame(cx, frame);
+ return GetFirstSubsumedFrame(cx, frame, skippedAsync);
}
/* static */ bool
@@ -437,7 +490,7 @@ public:
} // anonymous namespace
static inline js::SavedFrame *
-UnwrapSavedFrame(JSContext *cx, HandleObject obj)
+UnwrapSavedFrame(JSContext *cx, HandleObject obj, bool &skippedAsync)
{
if (!obj)
return nullptr;
@@ -445,14 +498,15 @@ UnwrapSavedFrame(JSContext *cx, HandleObject obj)
MOZ_ASSERT(savedFrameObj);
MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj));
js::RootedSavedFrame frame(cx, &savedFrameObj->as());
- return GetFirstSubsumedFrame(cx, frame);
+ return GetFirstSubsumedFrame(cx, frame, skippedAsync);
}
JS_PUBLIC_API(SavedFrameResult)
GetSavedFrameSource(JSContext *cx, HandleObject savedFrame, MutableHandleString sourcep)
{
AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
- js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
if (!frame) {
sourcep.set(cx->runtime()->emptyString);
return SavedFrameResult::AccessDenied;
@@ -466,7 +520,8 @@ GetSavedFrameLine(JSContext *cx, HandleObject savedFrame, uint32_t *linep)
{
MOZ_ASSERT(linep);
AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
- js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
if (!frame) {
*linep = 0;
return SavedFrameResult::AccessDenied;
@@ -480,7 +535,8 @@ GetSavedFrameColumn(JSContext *cx, HandleObject savedFrame, uint32_t *columnp)
{
MOZ_ASSERT(columnp);
AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
- js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
if (!frame) {
*columnp = 0;
return SavedFrameResult::AccessDenied;
@@ -493,7 +549,8 @@ JS_PUBLIC_API(SavedFrameResult)
GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, MutableHandleString namep)
{
AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
- js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
if (!frame) {
namep.set(nullptr);
return SavedFrameResult::AccessDenied;
@@ -502,17 +559,73 @@ GetSavedFrameFunctionDisplayName(JSContext *cx, HandleObject savedFrame, Mutable
return SavedFrameResult::Ok;
}
+JS_PUBLIC_API(SavedFrameResult)
+GetSavedFrameAsyncCause(JSContext *cx, HandleObject savedFrame, MutableHandleString asyncCausep)
+{
+ AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+ if (!frame) {
+ asyncCausep.set(nullptr);
+ return SavedFrameResult::AccessDenied;
+ }
+ asyncCausep.set(frame->getAsyncCause());
+ if (!asyncCausep && skippedAsync)
+ asyncCausep.set(cx->names().Async);
+ return SavedFrameResult::Ok;
+}
+
+JS_PUBLIC_API(SavedFrameResult)
+GetSavedFrameAsyncParent(JSContext *cx, HandleObject savedFrame, MutableHandleObject asyncParentp)
+{
+ AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
+ if (!frame) {
+ asyncParentp.set(nullptr);
+ return SavedFrameResult::AccessDenied;
+ }
+ js::RootedSavedFrame parent(cx, frame->getParent());
+
+ // The current value of |skippedAsync| is not interesting, because we are
+ // interested in whether we would cross any async parents to get from here
+ // to the first subsumed parent frame instead.
+ js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, skippedAsync));
+
+ // Even if |parent| is not subsumed, we still want to return a pointer to it
+ // rather than |subsumedParent| so it can pick up any |asyncCause| from the
+ // inaccessible part of the chain.
+ if (subsumedParent && (subsumedParent->getAsyncCause() || skippedAsync))
+ asyncParentp.set(parent);
+ else
+ asyncParentp.set(nullptr);
+ return SavedFrameResult::Ok;
+}
+
JS_PUBLIC_API(SavedFrameResult)
GetSavedFrameParent(JSContext *cx, HandleObject savedFrame, MutableHandleObject parentp)
{
AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
- js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame));
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, skippedAsync));
if (!frame) {
parentp.set(nullptr);
return SavedFrameResult::AccessDenied;
}
js::RootedSavedFrame parent(cx, frame->getParent());
- parentp.set(js::GetFirstSubsumedFrame(cx, parent));
+
+ // The current value of |skippedAsync| is not interesting, because we are
+ // interested in whether we would cross any async parents to get from here
+ // to the first subsumed parent frame instead.
+ js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, skippedAsync));
+
+ // Even if |parent| is not subsumed, we still want to return a pointer to it
+ // rather than |subsumedParent| so it can pick up any |asyncCause| from the
+ // inaccessible part of the chain.
+ if (subsumedParent && !(subsumedParent->getAsyncCause() || skippedAsync))
+ parentp.set(parent);
+ else
+ parentp.set(nullptr);
return SavedFrameResult::Ok;
}
@@ -520,7 +633,8 @@ JS_PUBLIC_API(bool)
StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString stringp)
{
AutoMaybeEnterFrameCompartment ac(cx, stack);
- js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack));
+ bool skippedAsync;
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, skippedAsync));
if (!frame) {
stringp.set(cx->runtime()->emptyString);
return true;
@@ -535,8 +649,13 @@ StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString
MOZ_ASSERT_IF(subsumes, (*subsumes)(principals, frame->getPrincipals()));
if (!frame->isSelfHosted()) {
+ RootedString asyncCause(cx, frame->getAsyncCause());
+ if (!asyncCause && skippedAsync) {
+ asyncCause.set(cx->names().Async);
+ }
js::RootedAtom name(cx, frame->getFunctionDisplayName());
- if ((name && !sb.append(name))
+ if ((asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
+ || (name && !sb.append(name))
|| !sb.append('@')
|| !sb.append(frame->getSource())
|| !sb.append(':')
@@ -550,7 +669,7 @@ StringifySavedFrameStack(JSContext *cx, HandleObject stack, MutableHandleString
}
parent = frame->getParent();
- frame = js::GetFirstSubsumedFrame(cx, parent);
+ frame = js::GetFirstSubsumedFrame(cx, parent, skippedAsync);
} while (frame);
JSString *str = sb.finishString();
@@ -613,6 +732,29 @@ SavedFrame::functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp)
return true;
}
+/* static */ bool
+SavedFrame::asyncCauseProperty(JSContext *cx, unsigned argc, Value *vp)
+{
+ THIS_SAVEDFRAME(cx, argc, vp, "(get asyncCause)", args, frame);
+ RootedString asyncCause(cx);
+ JS::SavedFrameResult result = JS::GetSavedFrameAsyncCause(cx, frame, &asyncCause);
+ if (result == JS::SavedFrameResult::Ok && asyncCause)
+ args.rval().setString(asyncCause);
+ else
+ args.rval().setNull();
+ return true;
+}
+
+/* static */ bool
+SavedFrame::asyncParentProperty(JSContext *cx, unsigned argc, Value *vp)
+{
+ THIS_SAVEDFRAME(cx, argc, vp, "(get asyncParent)", args, frame);
+ RootedObject asyncParent(cx);
+ (void) JS::GetSavedFrameAsyncParent(cx, frame, &asyncParent);
+ args.rval().setObjectOrNull(asyncParent);
+ return true;
+}
+
/* static */ bool
SavedFrame::parentProperty(JSContext *cx, unsigned argc, Value *vp)
{
@@ -672,12 +814,7 @@ SavedStacks::sweep(JSRuntime *rt)
}
if (obj != temp || parentMoved) {
- e.rekeyFront(SavedFrame::Lookup(frame->getSource(),
- frame->getLine(),
- frame->getColumn(),
- frame->getFunctionDisplayName(),
- frame->getParent(),
- frame->getPrincipals()),
+ e.rekeyFront(SavedFrame::Lookup(*frame),
ReadBarriered(frame));
}
}
@@ -738,9 +875,32 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
// pointers on the first pass, and then we fill in the parent pointers as we
// return in the second pass.
+ Activation *asyncActivation = nullptr;
+ RootedSavedFrame asyncStack(cx, nullptr);
+ RootedString asyncCause(cx, nullptr);
+
// Accumulate the vector of Lookup objects in |stackChain|.
SavedFrame::AutoLookupVector stackChain(cx);
while (!iter.done()) {
+ Activation &activation = *iter.activation();
+
+ if (!asyncActivation) {
+ asyncStack = activation.asyncStack();
+ if (asyncStack) {
+ // While walking from the youngest to the oldest frame, we found
+ // an activation that has an async stack set. We will use the
+ // youngest frame of the async stack as the parent of the oldest
+ // frame of this activation. We still need to iterate over other
+ // frames in this activation before reaching the oldest frame.
+ asyncCause = activation.asyncCause();
+ asyncActivation = &activation;
+ }
+ } else if (asyncActivation != &activation) {
+ // We found an async stack in the previous activation, and we
+ // walked past the oldest frame of that activation, we're done.
+ break;
+ }
+
AutoLocationValueRooter location(cx);
{
@@ -759,22 +919,79 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
location->column,
iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr,
nullptr,
+ nullptr,
iter.compartment()->principals
);
++iter;
- if (maxFrameCount == 0) {
- // If maxFrameCount is zero, then there's no limit on the number of
- // frames.
+ // If maxFrameCount is zero there's no limit on the number of frames.
+ if (maxFrameCount == 0)
continue;
- } else if (maxFrameCount == 1) {
- // Since we were only asked to save one frame, do not continue
- // walking the stack and saving frame state.
+
+ if (maxFrameCount == 1) {
+ // The frame we just saved was the last one we were asked to save.
+ // If we had an async stack, ensure we don't use any of its frames.
+ asyncStack.set(nullptr);
break;
- } else {
- maxFrameCount--;
}
+
+ maxFrameCount--;
+ }
+
+ // Limit the depth of the async stack, if any, and ensure that the
+ // SavedFrame instances we use are stored in the same compartment as the
+ // rest of the synchronous stack chain.
+ RootedSavedFrame parentFrame(cx, nullptr);
+ if (asyncStack && !adoptAsyncStack(cx, asyncStack, asyncCause, &parentFrame, maxFrameCount))
+ return false;
+
+ // Iterate through |stackChain| in reverse order and get or create the
+ // actual SavedFrame instances.
+ for (size_t i = stackChain->length(); i != 0; i--) {
+ SavedFrame::HandleLookup lookup = stackChain[i-1];
+ lookup->parent = parentFrame;
+ parentFrame.set(getOrCreateSavedFrame(cx, lookup));
+ if (!parentFrame)
+ return false;
+ }
+
+ frame.set(parentFrame);
+ return true;
+}
+
+bool
+SavedStacks::adoptAsyncStack(JSContext *cx, HandleSavedFrame asyncStack,
+ HandleString asyncCause,
+ MutableHandleSavedFrame adoptedStack,
+ unsigned maxFrameCount)
+{
+ RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause));
+ if (!asyncCauseAtom)
+ return false;
+
+ // If maxFrameCount is zero, the caller asked for an unlimited number of
+ // stack frames, but async stacks are not limited by the available stack
+ // memory, so we need to set an arbitrary limit when collecting them. We
+ // still don't enforce an upper limit if the caller requested more frames.
+ if (maxFrameCount == 0)
+ maxFrameCount = ASYNC_STACK_MAX_FRAME_COUNT;
+
+ // Accumulate the vector of Lookup objects in |stackChain|.
+ SavedFrame::AutoLookupVector stackChain(cx);
+ SavedFrame *currentSavedFrame = asyncStack;
+ for (unsigned i = 0; i < maxFrameCount && currentSavedFrame; i++) {
+ // Use growByUninitialized and placement-new instead of just append.
+ // We'd ideally like to use an emplace method once Vector supports it.
+ if (!stackChain->growByUninitialized(1))
+ return false;
+ new (&stackChain->back()) SavedFrame::Lookup(*currentSavedFrame);
+
+ // Attach the asyncCause to the youngest frame.
+ if (i == 0)
+ stackChain->back().asyncCause = asyncCauseAtom;
+
+ currentSavedFrame = currentSavedFrame->getParent();
}
// Iterate through |stackChain| in reverse order and get or create the
@@ -788,7 +1005,7 @@ SavedStacks::insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFram
return false;
}
- frame.set(parentFrame);
+ adoptedStack.set(parentFrame);
return true;
}
diff --git a/js/src/vm/SavedStacks.h b/js/src/vm/SavedStacks.h
index 4e4199a400b0..da820230aa1a 100644
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -35,6 +35,8 @@ class SavedFrame : public NativeObject {
static bool lineProperty(JSContext *cx, unsigned argc, Value *vp);
static bool columnProperty(JSContext *cx, unsigned argc, Value *vp);
static bool functionDisplayNameProperty(JSContext *cx, unsigned argc, Value *vp);
+ static bool asyncCauseProperty(JSContext *cx, unsigned argc, Value *vp);
+ static bool asyncParentProperty(JSContext *cx, unsigned argc, Value *vp);
static bool parentProperty(JSContext *cx, unsigned argc, Value *vp);
static bool toStringMethod(JSContext *cx, unsigned argc, Value *vp);
@@ -43,6 +45,7 @@ class SavedFrame : public NativeObject {
uint32_t getLine();
uint32_t getColumn();
JSAtom *getFunctionDisplayName();
+ JSAtom *getAsyncCause();
SavedFrame *getParent();
JSPrincipals *getPrincipals();
@@ -60,8 +63,6 @@ class SavedFrame : public NativeObject {
HashPolicy,
SystemAllocPolicy> Set;
- typedef RootedGeneric AutoLookupRooter;
-
class AutoLookupVector;
class MOZ_STACK_CLASS HandleLookup {
@@ -86,6 +87,7 @@ class SavedFrame : public NativeObject {
JSSLOT_LINE,
JSSLOT_COLUMN,
JSSLOT_FUNCTIONDISPLAYNAME,
+ JSSLOT_ASYNCCAUSE,
JSSLOT_PARENT,
JSSLOT_PRINCIPALS,
JSSLOT_PRIVATE_PARENT,
@@ -156,6 +158,10 @@ class SavedStacks {
bool insertFrames(JSContext *cx, FrameIter &iter, MutableHandleSavedFrame frame,
unsigned maxFrameCount = 0);
+ bool adoptAsyncStack(JSContext *cx, HandleSavedFrame asyncStack,
+ HandleString asyncCause,
+ MutableHandleSavedFrame adoptedStack,
+ unsigned maxFrameCount);
SavedFrame *getOrCreateSavedFrame(JSContext *cx, SavedFrame::HandleLookup lookup);
SavedFrame *createFrameFromLookup(JSContext *cx, SavedFrame::HandleLookup lookup);
void chooseSamplingProbability(JSContext* cx);
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index 070e53d02cee..861efb8d8975 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -6,6 +6,7 @@
#include "vm/SelfHosting.h"
+#include "mozilla/Casting.h"
#include "mozilla/DebugOnly.h"
#include "jscntxt.h"
@@ -653,8 +654,47 @@ js::intrinsic_IsTypedArray(JSContext *cx, unsigned argc, Value *vp)
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
- RootedObject obj(cx, &args[0].toObject());
- args.rval().setBoolean(obj->is());
+ args.rval().setBoolean(args[0].toObject().is());
+ return true;
+}
+
+bool
+js::intrinsic_TypedArrayBuffer(JSContext *cx, unsigned argc, Value *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(TypedArrayObject::is(args[0]));
+
+ Rooted tarray(cx, &args[0].toObject().as());
+ if (!TypedArrayObject::ensureHasBuffer(cx, tarray))
+ return false;
+
+ args.rval().set(TypedArrayObject::bufferValue(tarray));
+ return true;
+}
+
+bool
+js::intrinsic_TypedArrayByteOffset(JSContext *cx, unsigned argc, Value *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(TypedArrayObject::is(args[0]));
+
+ args.rval().set(TypedArrayObject::byteOffsetValue(&args[0].toObject().as()));
+ return true;
+}
+
+bool
+js::intrinsic_TypedArrayElementShift(JSContext *cx, unsigned argc, Value *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+ MOZ_ASSERT(TypedArrayObject::is(args[0]));
+
+ unsigned shift = TypedArrayShift(args[0].toObject().as().type());
+ MOZ_ASSERT(shift == 0 || shift == 1 || shift == 2 || shift == 3);
+
+ args.rval().setInt32(mozilla::AssertedCast(shift));
return true;
}
@@ -671,6 +711,57 @@ js::intrinsic_TypedArrayLength(JSContext *cx, unsigned argc, Value *vp)
return true;
}
+bool
+js::intrinsic_MoveTypedArrayElements(JSContext *cx, unsigned argc, Value *vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 4);
+
+ Rooted tarray(cx, &args[0].toObject().as());
+ uint32_t to = uint32_t(args[1].toInt32());
+ uint32_t from = uint32_t(args[2].toInt32());
+ uint32_t count = uint32_t(args[3].toInt32());
+
+ MOZ_ASSERT(count > 0,
+ "don't call this method if copying no elements, because then "
+ "the not-neutered requirement is wrong");
+
+ if (tarray->hasBuffer() && tarray->buffer()->isNeutered()) {
+ JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+ return false;
+ }
+
+ // Don't multiply by |tarray->bytesPerElement()| in case the compiler can't
+ // strength-reduce multiplication by 1/2/4/8 into the equivalent shift.
+ const size_t ElementShift = TypedArrayShift(tarray->type());
+
+ MOZ_ASSERT((UINT32_MAX >> ElementShift) > to);
+ uint32_t byteDest = to << ElementShift;
+
+ MOZ_ASSERT((UINT32_MAX >> ElementShift) > from);
+ uint32_t byteSrc = from << ElementShift;
+
+ MOZ_ASSERT((UINT32_MAX >> ElementShift) >= count);
+ uint32_t byteSize = count << ElementShift;
+
+#ifdef DEBUG
+ {
+ uint32_t viewByteLength = tarray->byteLength();
+ MOZ_ASSERT(byteSize <= viewByteLength);
+ MOZ_ASSERT(byteDest < viewByteLength);
+ MOZ_ASSERT(byteSrc < viewByteLength);
+ MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
+ MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
+ }
+#endif
+
+ uint8_t *data = static_cast(tarray->viewData());
+ mozilla::PodMove(&data[byteDest], &data[byteSrc], byteSize);
+
+ args.rval().setUndefined();
+ return true;
+}
+
bool
CallSelfHostedNonGenericMethod(JSContext *cx, CallArgs args)
{
@@ -907,8 +998,13 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("GeneratorSetClosed", intrinsic_GeneratorSetClosed, 1,0),
JS_FN("IsTypedArray", intrinsic_IsTypedArray, 1,0),
+ JS_FN("TypedArrayBuffer", intrinsic_TypedArrayBuffer, 1,0),
+ JS_FN("TypedArrayByteOffset", intrinsic_TypedArrayByteOffset, 1,0),
+ JS_FN("TypedArrayElementShift", intrinsic_TypedArrayElementShift, 1,0),
JS_FN("TypedArrayLength", intrinsic_TypedArrayLength, 1,0),
+ JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
+
JS_FN("CallTypedArrayMethodIfWrapped",
CallNonGenericSelfhostedMethod