зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound. a=merge
This commit is contained in:
Коммит
d00595bc6b
|
@ -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");
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>fxa_profile_channel_test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.onload = function(){
|
||||
var event = new window.CustomEvent("WebChannelMessageToChrome", {
|
||||
detail: {
|
||||
id: "account_updates",
|
||||
message: {
|
||||
command: "profile:image:change",
|
||||
data: {
|
||||
uid: "abc123",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
window.dispatchEvent(event);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,90 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
|
||||
return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {});
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfileChannel",
|
||||
"resource://gre/modules/FxAccountsProfileChannel.jsm");
|
||||
|
||||
const HTTP_PATH = "http://example.com";
|
||||
|
||||
let gTests = [
|
||||
{
|
||||
desc: "FxA Profile Channel - should receive message about account updates",
|
||||
run: function* () {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let tabOpened = false;
|
||||
let properUrl = "http://example.com/browser/browser/base/content/test/general/browser_fxa_profile_channel.html";
|
||||
|
||||
waitForTab(function (tab) {
|
||||
Assert.ok("Tab successfully opened");
|
||||
let match = gBrowser.currentURI.spec == properUrl;
|
||||
Assert.ok(match);
|
||||
|
||||
tabOpened = true;
|
||||
});
|
||||
|
||||
let client = new FxAccountsProfileChannel({
|
||||
content_uri: HTTP_PATH,
|
||||
});
|
||||
|
||||
makeObserver(FxAccountsCommon.ONPROFILE_IMAGE_CHANGE_NOTIFICATION, function (subject, topic, data) {
|
||||
Assert.ok(tabOpened);
|
||||
Assert.equal(data, "abc123");
|
||||
resolve();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab(properUrl);
|
||||
});
|
||||
}
|
||||
}
|
||||
]; // gTests
|
||||
|
||||
function makeObserver(aObserveTopic, aObserveFunc) {
|
||||
let callback = function (aSubject, aTopic, aData) {
|
||||
if (aTopic == aObserveTopic) {
|
||||
removeMe();
|
||||
aObserveFunc(aSubject, aTopic, aData);
|
||||
}
|
||||
};
|
||||
|
||||
function removeMe() {
|
||||
Services.obs.removeObserver(callback, aObserveTopic);
|
||||
}
|
||||
|
||||
Services.obs.addObserver(callback, aObserveTopic, false);
|
||||
return removeMe;
|
||||
}
|
||||
|
||||
function waitForTab(aCallback) {
|
||||
let container = gBrowser.tabContainer;
|
||||
container.addEventListener("TabOpen", function tabOpener(event) {
|
||||
container.removeEventListener("TabOpen", tabOpener, false);
|
||||
gBrowser.addEventListener("load", function listener() {
|
||||
gBrowser.removeEventListener("load", listener, true);
|
||||
let tab = event.target;
|
||||
aCallback(tab);
|
||||
}, true);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function () {
|
||||
for (let test of gTests) {
|
||||
info("Running: " + test.desc);
|
||||
yield test.run();
|
||||
}
|
||||
}).then(finish, ex => {
|
||||
Assert.ok(false, "Unexpected Exception: " + ex);
|
||||
finish();
|
||||
});
|
||||
}
|
|
@ -414,29 +414,36 @@ function waitForDocLoadAndStopIt(aExpectedURL, aBrowser=gBrowser.selectedBrowser
|
|||
* @return promise
|
||||
*/
|
||||
function waitForDocLoadComplete(aBrowser=gBrowser) {
|
||||
let deferred = Promise.defer();
|
||||
let progressListener = {
|
||||
onStateChange: function (webProgress, req, flags, status) {
|
||||
let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
|
||||
Ci.nsIWebProgressListener.STATE_STOP;
|
||||
info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
|
||||
return new Promise(resolve => {
|
||||
let listener = {
|
||||
onStateChange: function (webProgress, req, flags, status) {
|
||||
let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
|
||||
Ci.nsIWebProgressListener.STATE_STOP;
|
||||
info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
|
||||
|
||||
// When a load needs to be retargetted to a new process it is cancelled
|
||||
// with NS_BINDING_ABORTED so ignore that case
|
||||
if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
|
||||
aBrowser.removeProgressListener(progressListener);
|
||||
info("Browser loaded " + aBrowser.contentWindow.location);
|
||||
deferred.resolve();
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
aBrowser.addProgressListener(progressListener);
|
||||
info("Waiting for browser load");
|
||||
return deferred.promise;
|
||||
// When a load needs to be retargetted to a new process it is cancelled
|
||||
// with NS_BINDING_ABORTED so ignore that case
|
||||
if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
|
||||
aBrowser.removeProgressListener(this);
|
||||
waitForDocLoadComplete.listeners.delete(this);
|
||||
info("Browser loaded " + aBrowser.contentWindow.location);
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
aBrowser.addProgressListener(listener);
|
||||
waitForDocLoadComplete.listeners.add(listener);
|
||||
info("Waiting for browser load");
|
||||
});
|
||||
}
|
||||
|
||||
// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
|
||||
// they're not GC'ed before we saw the page load.
|
||||
waitForDocLoadComplete.listeners = new Set();
|
||||
registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
|
||||
|
||||
let FullZoomHelper = {
|
||||
|
||||
selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
with Files('*'):
|
||||
BUG_COMPONENT = ('Firefox', 'Downloads Panel')
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
|
||||
|
||||
|
|
|
@ -166,7 +166,8 @@ loop.roomViews = (function(mozL10n) {
|
|||
ActiveRoomStoreMixin,
|
||||
sharedMixins.DocumentTitleMixin,
|
||||
sharedMixins.MediaSetupMixin,
|
||||
sharedMixins.RoomsAudioMixin
|
||||
sharedMixins.RoomsAudioMixin,
|
||||
sharedMixins.WindowCloseMixin
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
|
@ -204,14 +205,11 @@ loop.roomViews = (function(mozL10n) {
|
|||
* User clicked on the "Leave" button.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the window if the cancel button is pressed in the generic failure view.
|
||||
*/
|
||||
closeWindow: function() {
|
||||
window.close();
|
||||
if (this.state.used) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
} else {
|
||||
this.closeWindow();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -255,15 +253,9 @@ loop.roomViews = (function(mozL10n) {
|
|||
);
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
if (this.state.used)
|
||||
return React.createElement(sharedViews.FeedbackView, {
|
||||
onAfterFeedbackReceived: this.closeWindow}
|
||||
);
|
||||
|
||||
// In case the room was not used (no one was here), we
|
||||
// bypass the feedback form.
|
||||
this.closeWindow();
|
||||
return null;
|
||||
return React.createElement(sharedViews.FeedbackView, {
|
||||
onAfterFeedbackReceived: this.closeWindow}
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
|
|
|
@ -166,7 +166,8 @@ loop.roomViews = (function(mozL10n) {
|
|||
ActiveRoomStoreMixin,
|
||||
sharedMixins.DocumentTitleMixin,
|
||||
sharedMixins.MediaSetupMixin,
|
||||
sharedMixins.RoomsAudioMixin
|
||||
sharedMixins.RoomsAudioMixin,
|
||||
sharedMixins.WindowCloseMixin
|
||||
],
|
||||
|
||||
propTypes: {
|
||||
|
@ -204,14 +205,11 @@ loop.roomViews = (function(mozL10n) {
|
|||
* User clicked on the "Leave" button.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the window if the cancel button is pressed in the generic failure view.
|
||||
*/
|
||||
closeWindow: function() {
|
||||
window.close();
|
||||
if (this.state.used) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.LeaveRoom());
|
||||
} else {
|
||||
this.closeWindow();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -255,15 +253,9 @@ loop.roomViews = (function(mozL10n) {
|
|||
/>;
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
if (this.state.used)
|
||||
return <sharedViews.FeedbackView
|
||||
onAfterFeedbackReceived={this.closeWindow}
|
||||
/>;
|
||||
|
||||
// In case the room was not used (no one was here), we
|
||||
// bypass the feedback form.
|
||||
this.closeWindow();
|
||||
return null;
|
||||
return <sharedViews.FeedbackView
|
||||
onAfterFeedbackReceived={this.closeWindow}
|
||||
/>;
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
|
|
|
@ -497,13 +497,6 @@ loop.store.ActiveRoomStore = (function() {
|
|||
windowUnload: function() {
|
||||
this._leaveRoom(ROOM_STATES.CLOSING);
|
||||
|
||||
// If we're closing the window, then ensure the screensharing state
|
||||
// is cleared. We don't do this on leave room, as we might still be
|
||||
// sharing.
|
||||
this._mozLoop.setScreenShareState(
|
||||
this.getStoreState().windowId,
|
||||
false);
|
||||
|
||||
if (!this._onUpdateListener) {
|
||||
return;
|
||||
}
|
||||
|
@ -520,7 +513,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
* Handles a room being left.
|
||||
*/
|
||||
leaveRoom: function() {
|
||||
this._leaveRoom();
|
||||
this._leaveRoom(ROOM_STATES.ENDED);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -554,14 +547,24 @@ loop.store.ActiveRoomStore = (function() {
|
|||
* Handles leaving a room. Clears any membership timeouts, then
|
||||
* signals to the server the leave of the room.
|
||||
*
|
||||
* @param {ROOM_STATES} nextState Optional; the next state to switch to.
|
||||
* Switches to READY if undefined.
|
||||
* @param {ROOM_STATES} nextState The next state to switch to.
|
||||
*/
|
||||
_leaveRoom: function(nextState) {
|
||||
if (loop.standaloneMedia) {
|
||||
loop.standaloneMedia.multiplexGum.reset();
|
||||
}
|
||||
|
||||
this._mozLoop.setScreenShareState(
|
||||
this.getStoreState().windowId,
|
||||
false);
|
||||
|
||||
if (this._browserSharingListener) {
|
||||
// Remove the browser sharing listener as we don't need it now.
|
||||
this._mozLoop.removeBrowserSharingListener(this._browserSharingListener);
|
||||
this._browserSharingListener = null;
|
||||
}
|
||||
|
||||
// We probably don't need to end screen share separately, but lets be safe.
|
||||
this._sdkDriver.disconnectSession();
|
||||
|
||||
if (this._timeout) {
|
||||
|
@ -577,7 +580,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
this._storeState.sessionToken);
|
||||
}
|
||||
|
||||
this.setStoreState({roomState: nextState || ROOM_STATES.ENDED});
|
||||
this.setStoreState({roomState: nextState});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,7 @@ loop.store.ROOM_STATES = {
|
|||
FAILED: "room-failed",
|
||||
// The room is full
|
||||
FULL: "room-full",
|
||||
// The room conversation has ended
|
||||
// The room conversation has ended, displays the feedback view.
|
||||
ENDED: "room-ended",
|
||||
// The window is closing
|
||||
CLOSING: "room-closing"
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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<number> 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<null> 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<null> 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<null> 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<null> 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<Sqlite.OpenedConnection>
|
||||
_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];
|
||||
}
|
|
@ -6,6 +6,7 @@ JAR_MANIFESTS += ['jar.mn']
|
|||
|
||||
EXTRA_JS_MODULES.readinglist += [
|
||||
'ReadingList.jsm',
|
||||
'SQLiteStore.jsm',
|
||||
]
|
||||
|
||||
TESTING_JS_MODULES += [
|
||||
|
|
|
@ -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,
|
||||
|
||||
/**
|
||||
* <template> 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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -2,4 +2,6 @@
|
|||
head = head.js
|
||||
firefox-appdir = browser
|
||||
|
||||
;[test_ReadingList.js]
|
||||
[test_scheduler.js]
|
||||
;[test_SQLiteStore.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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -110,6 +110,7 @@ devtoolsCommandlineHandler.prototype = {
|
|||
let debuggerServer = serverLoader.DebuggerServer;
|
||||
debuggerServer.init();
|
||||
debuggerServer.addBrowserActors();
|
||||
debuggerServer.allowChromeProcess = true;
|
||||
|
||||
let listener = debuggerServer.createListener();
|
||||
listener.portOrPath = portOrPath;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -575,6 +575,7 @@ let gLocalRuntime = {
|
|||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
connection.host = null; // Force Pipe transport
|
||||
connection.port = null;
|
||||
connection.connect();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -525,7 +525,6 @@ SECKEY_ExtractPublicKey
|
|||
SECKEY_GetPublicKeyType
|
||||
SECKEY_ImportDERPublicKey
|
||||
SECKEY_PublicKeyStrength
|
||||
SECKEY_PublicKeyStrengthInBits
|
||||
SECKEY_RSAPSSParamsTemplate DATA
|
||||
SECKEY_SignatureLen
|
||||
SECMIME_DecryptionAllowed
|
||||
|
|
|
@ -858,7 +858,7 @@ Console::Assert(JSContext* aCx, bool aCondition,
|
|||
METHOD(Count, "count")
|
||||
|
||||
void
|
||||
Console::__noSuchMethod__()
|
||||
Console::NoopMethod()
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ public:
|
|||
Count(JSContext* aCx, const Sequence<JS::Value>& aData);
|
||||
|
||||
void
|
||||
__noSuchMethod__();
|
||||
NoopMethod();
|
||||
|
||||
private:
|
||||
enum MethodName
|
||||
|
|
|
@ -723,31 +723,5 @@ DOMException::Create(nsresult aRv)
|
|||
return inst.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
DOMException::Sanitize(JSContext* aCx,
|
||||
JS::MutableHandle<JS::Value> aSanitizedValue)
|
||||
{
|
||||
nsRefPtr<DOMException> 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<nsIStackFrame> 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
|
||||
|
|
|
@ -156,15 +156,6 @@ public:
|
|||
static already_AddRefed<DOMException>
|
||||
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<JS::Value> aSanitizedValue);
|
||||
|
||||
protected:
|
||||
|
||||
virtual ~DOMException() {}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<nsEventTargetSH>,
|
||||
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<nsDOMGenericSH>,
|
||||
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<typename Super>
|
||||
NS_IMETHODIMP
|
||||
nsMessageManagerSH<Super>::Resolve(nsIXPConnectWrappedNative* wrapper,
|
||||
JSContext* cx, JSObject* obj_,
|
||||
jsid id_, bool* resolvedp,
|
||||
bool* _retval)
|
||||
{
|
||||
JS::Rooted<JSObject*> obj(cx, obj_);
|
||||
JS::Rooted<jsid> 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<typename Super>
|
||||
NS_IMETHODIMP
|
||||
nsMessageManagerSH<Super>::Enumerate(nsIXPConnectWrappedNative* wrapper,
|
||||
JSContext* cx, JSObject* obj_,
|
||||
bool* _retval)
|
||||
{
|
||||
JS::Rooted<JSObject*> 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;
|
||||
}
|
||||
|
|
|
@ -313,4 +313,29 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
template<typename Super>
|
||||
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___ */
|
||||
|
|
|
@ -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<nsPIDOMWindow> 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<AsyncEventDispatcher> 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<AsyncEventDispatcher> event =
|
||||
new AsyncEventDispatcher(mOwnerContent,
|
||||
NS_LITERAL_STRING("MozLayerTreeCleared"),
|
||||
true, false);
|
||||
event->PostDOMEvent();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -670,6 +670,26 @@ public:
|
|||
mCleanedUp);
|
||||
}
|
||||
|
||||
bool
|
||||
HadOriginalOpener() const
|
||||
{
|
||||
MOZ_ASSERT(IsOuterWindow());
|
||||
return mHadOriginalOpener;
|
||||
}
|
||||
|
||||
bool
|
||||
IsTopLevelWindow()
|
||||
{
|
||||
MOZ_ASSERT(IsOuterWindow());
|
||||
nsCOMPtr<nsIDOMWindow> parentWindow;
|
||||
nsresult rv = GetScriptableTop(getter_AddRefs(parentWindow));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parentWindow == static_cast<nsIDOMWindow*>(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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1139964
|
||||
-->
|
||||
<window title="Mozilla Bug 1139964"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="run()">
|
||||
<label value="Mozilla Bug 1139964"/>
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript"><![CDATA[
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
|
||||
function ok(cond, msg) {
|
||||
opener.wrappedJSObject.ok(cond, msg);
|
||||
}
|
||||
|
||||
var msgName = "TEST:Global_has_Promise";
|
||||
|
||||
function mmScriptForPromiseTest() {
|
||||
sendAsyncMessage("TEST:Global_has_Promise",
|
||||
{
|
||||
hasPromise: ("Promise" in this),
|
||||
hasTextEncoder: ("TextEncoder" in this),
|
||||
hasWindow: ("Window" in this),
|
||||
});
|
||||
}
|
||||
|
||||
function processListener(m) {
|
||||
ppm.removeMessageListener(msgName, processListener);
|
||||
ok(m.data.hasPromise, "ProcessGlobal should have Promise object in the global scope!");
|
||||
ok(m.data.hasTextEncoder, "ProcessGlobal should have TextEncoder object in the global scope!");
|
||||
ok(!m.data.hasWindow, "ProcessGlobal should not have Window object in the global scope!");
|
||||
|
||||
messageManager.addMessageListener(msgName, tabListener)
|
||||
messageManager.loadFrameScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true);
|
||||
}
|
||||
|
||||
function tabListener(m) {
|
||||
messageManager.removeMessageListener(msgName, tabListener);
|
||||
ok(m.data.hasPromise, "TabChildGlobal should have Promise object in the global scope!");
|
||||
ok(m.data.hasTextEncoder, "TabChildGlobal should have TextEncoder object in the global scope!");
|
||||
ok(!m.data.hasWindow, "TabChildGlobal should not have Window object in the global scope!");
|
||||
|
||||
opener.setTimeout("done()", 0);
|
||||
window.close();
|
||||
}
|
||||
|
||||
function run() {
|
||||
ppm.addMessageListener(msgName, processListener)
|
||||
ppm.loadProcessScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true);
|
||||
}
|
||||
|
||||
]]></script>
|
||||
<browser type="content" src="about:blank" id="ifr"/>
|
||||
</window>
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1139964
|
||||
-->
|
||||
<window title="Mozilla Bug 1139964"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1139964"
|
||||
target="_blank">Mozilla Bug 1139964</a>
|
||||
</body>
|
||||
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript"><![CDATA[
|
||||
|
||||
/** Test for Bug 1139964 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function done() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
addLoadEvent(function() {
|
||||
window.open("file_bug1139964.xul", "", "chrome");
|
||||
});
|
||||
]]></script>
|
||||
</window>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bug 1139667 - Test mapping of fetch() to connect-src</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
|
||||
// Please note that file_csp_testserver.sjs?foo does not return a response.
|
||||
// For testing purposes this is not necessary because we only want to check
|
||||
// whether CSP allows or blocks the load.
|
||||
fetch( "file_csp_testserver.sjs?foo");
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bug 1031530 - Test mapping of XMLHttpRequest to connect-src</title>
|
||||
<title>Bug 1031530 and Bug 1139667 - Test mapping of XMLHttpRequest and fetch() to connect-src</title>
|
||||
<!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
|
@ -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);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -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<JS::Value> 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<JSObject*> 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<JSObject*> aObj,
|
||||
JS::Handle<jsid> 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<JSObject*> 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<JSObject*> src,
|
|||
return wrappedJS->QueryInterface(iid, ppArg);
|
||||
}
|
||||
|
||||
bool
|
||||
SystemGlobalResolve(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
JS::Handle<jsid> 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<JSObject*> obj)
|
||||
{
|
||||
bool ignored = false;
|
||||
return EnumerateGlobal(cx, obj) &&
|
||||
ResolveSystemBinding(cx, obj, JSID_VOIDHANDLE, &ignored);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -3270,6 +3270,18 @@ GetErrorPrototype(JSContext* aCx, JS::Handle<JSObject*> 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<JSObject*> obj,
|
||||
JS::Handle<jsid> 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<JSObject*> obj);
|
||||
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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<JS::Value> value);
|
||||
|
||||
void MOZ_ALWAYS_INLINE MightThrowJSException()
|
||||
|
|
|
@ -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<typename ReturnType, typename GetterOutParamType>
|
||||
static void
|
||||
GetValueIfNotCached(JSContext* aCx, JSObject* aStack,
|
||||
JS::SavedFrameResult (*aPropGetter)(JSContext*,
|
||||
JS::Handle<JSObject*>,
|
||||
GetterOutParamType),
|
||||
bool aIsCached, bool* aCanCache, bool* aUseCachedValue,
|
||||
ReturnType aValue)
|
||||
{
|
||||
MOZ_ASSERT(aStack);
|
||||
|
||||
JS::Rooted<JSObject*> 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<JSObject*> stack(cx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
NS_ENSURE_TRUE(mStack, NS_ERROR_NOT_AVAILABLE);
|
||||
ThreadsafeAutoJSContext cx;
|
||||
JS::Rooted<JSString*> filename(cx);
|
||||
bool canCache = false, useCachedValue = false;
|
||||
GetValueIfNotCached(cx, mStack, JS::GetSavedFrameSource, mFilenameInitialized,
|
||||
&canCache, &useCachedValue, &filename);
|
||||
if (useCachedValue) {
|
||||
return StackFrame::GetFilename(aFilename);
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> 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<JSObject*> stack(cx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
JS::Rooted<JS::Value> 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<JSString*> 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<JSObject*> stack(cx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
JS::Rooted<JS::Value> 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<JSObject*> stack(cx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
JS::Rooted<JS::Value> 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<nsIStackFrame> 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<JSObject*> stack(cx, mStack);
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, stack);
|
||||
JS::Rooted<JS::Value> 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<JSObject*> callerObj(cx);
|
||||
bool canCache = false, useCachedValue = false;
|
||||
GetValueIfNotCached(cx, mStack, JS::GetSavedFrameParent, mCallerInitialized,
|
||||
&canCache, &useCachedValue, &callerObj);
|
||||
|
||||
if (callerVal.isObject()) {
|
||||
JS::Rooted<JSObject*> 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<nsIStackFrame> 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<JS::Value> stack(cx, JS::ObjectValue(*mStack));
|
||||
JS::ExposeObjectToActiveJS(mStack);
|
||||
JSAutoCompartment ac(cx, mStack);
|
||||
JS::Rooted<JSString*> 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<JSObject*> stack(cx, mStack);
|
||||
|
||||
JS::Rooted<JSString*> 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<JSObject*> 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<nsIStackFrame>
|
||||
JSStackFrame::CreateStack(JSContext* aCx, int32_t aMaxDepth)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<EntryId, 256> 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<mozIStorageStatement> 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; }
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ struct PCacheQueryParams
|
|||
bool ignoreSearch;
|
||||
bool ignoreMethod;
|
||||
bool ignoreVary;
|
||||
bool prefixMatch;
|
||||
bool cacheNameSet;
|
||||
nsString cacheName;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -131,6 +131,26 @@ DragEvent::GetDataTransfer()
|
|||
return dragEvent->dataTransfer;
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<DragEvent>
|
||||
DragEvent::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aType,
|
||||
const DragEventInit& aParam,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
nsRefPtr<DragEvent> 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
|
||||
|
||||
|
|
|
@ -48,6 +48,11 @@ public:
|
|||
DataTransfer* aDataTransfer,
|
||||
ErrorResult& aError);
|
||||
|
||||
static already_AddRefed<DragEvent> Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aType,
|
||||
const DragEventInit& aParam,
|
||||
ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
~DragEvent() {}
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -177,7 +177,7 @@ public:
|
|||
nsRefPtr<FetchDriver> 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<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
|
||||
nsRefPtr<FetchDriver> 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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<nsIIOService> 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
|
||||
|
|
|
@ -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<nsIPrincipal> mPrincipal;
|
||||
|
@ -76,8 +72,8 @@ private:
|
|||
nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
|
||||
nsCOMPtr<nsIChannel> mOldRedirectChannel;
|
||||
nsCOMPtr<nsIChannel> mNewRedirectChannel;
|
||||
nsCOMPtr<nsIDocument> mDocument;
|
||||
uint32_t mFetchRecursionCount;
|
||||
net::ReferrerPolicy mReferrerPolicy;
|
||||
|
||||
DebugOnly<bool> mResponseAvailableCalled;
|
||||
|
||||
|
|
|
@ -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<File> newBlob = new File(mGlobal, blob->Impl());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<LayerTreeUpdateRunnable> 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<mozilla::dom::EventTarget> target = do_QueryInterface(mFrameElement);
|
||||
if (!target) {
|
||||
NS_WARNING("Could not locate target for layer tree message.");
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMEvent> 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()
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче