merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-04-14 14:27:14 +02:00
commit 8b35717b4c
33 changed files with 1066 additions and 633 deletions

View File

@ -944,6 +944,7 @@ var gBrowserInit = {
DevEdition.init();
let mm = window.getGroupMessageManager("browsers");
mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
mm.loadFrameScript("chrome://browser/content/content.js", true);
mm.loadFrameScript("chrome://browser/content/content-UITour.js", true);

View File

@ -3,6 +3,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* This content script should work in any browser or iframe and should not
* depend on the frame being contained in tabbrowser. */
let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -12,8 +15,6 @@ Cu.import("resource:///modules/ContentObservers.jsm");
Cu.import("resource://gre/modules/InlineSpellChecker.jsm");
Cu.import("resource://gre/modules/InlineSpellCheckerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
"resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
@ -28,28 +29,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
"resource:///modules/FormSubmitObserver.jsm");
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
ssdp.registerDevice({
id: "roku:ecp",
target: "roku:ecp",
factory: function(aService) {
Cu.import("resource://gre/modules/RokuApp.jsm");
return new RokuApp(aService);
},
mirror: true,
types: ["video/mp4"],
extensions: ["mp4"]
});
return ssdp;
});
XPCOMUtils.defineLazyGetter(this, "PageMenuChild", function() {
let tmp = {};
Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
@ -62,61 +43,6 @@ var global = this;
// Load the form validation popup handler
var formSubmitObserver = new FormSubmitObserver(content, this);
addMessageListener("Browser:HideSessionRestoreButton", function (message) {
// Hide session restore button on about:home
let doc = content.document;
let container;
if (doc.documentURI.toLowerCase() == "about:home" &&
(container = doc.getElementById("sessionRestoreContainer"))) {
container.hidden = true;
}
});
addMessageListener("Browser:Reload", function(message) {
/* First, we'll try to use the session history object to reload so
* that framesets are handled properly. If we're in a special
* window (such as view-source) that has no session history, fall
* back on using the web navigation's reload method.
*/
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
try {
let sh = webNav.sessionHistory;
if (sh)
webNav = sh.QueryInterface(Ci.nsIWebNavigation);
} catch (e) {
}
let reloadFlags = message.data.flags;
let handlingUserInput;
try {
handlingUserInput = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.setHandlingUserInput(message.data.handlingUserInput);
webNav.reload(reloadFlags);
} catch (e) {
} finally {
handlingUserInput.destruct();
}
});
addMessageListener("MixedContent:ReenableProtection", function() {
docShell.mixedContentChannel = null;
});
addMessageListener("SecondScreen:tab-mirror", function(message) {
if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
return;
}
let app = SimpleServiceDiscovery.findAppForService(message.data.service);
if (app) {
let width = content.innerWidth;
let height = content.innerHeight;
let viewport = {cssWidth: width, cssHeight: height, width: width, height: height};
app.mirror(function() {}, content, viewport, function() {}, content);
}
});
addMessageListener("ContextMenu:DoCustomCommand", function(message) {
PageMenuChild.executeMenu(message.data);
});
@ -338,254 +264,6 @@ let AboutNetErrorListener = {
AboutNetErrorListener.init(this);
let AboutHomeListener = {
init: function(chromeGlobal) {
chromeGlobal.addEventListener('AboutHomeLoad', this, false, true);
},
get isAboutHome() {
return content.document.documentURI.toLowerCase() == "about:home";
},
handleEvent: function(aEvent) {
if (!this.isAboutHome) {
return;
}
switch (aEvent.type) {
case "AboutHomeLoad":
this.onPageLoad();
break;
case "AboutHomeSearchEvent":
this.onSearch(aEvent);
break;
case "AboutHomeSearchPanel":
this.onOpenSearchPanel(aEvent);
break;
case "click":
this.onClick(aEvent);
break;
case "pagehide":
this.onPageHide(aEvent);
break;
}
},
receiveMessage: function(aMessage) {
if (!this.isAboutHome) {
return;
}
switch (aMessage.name) {
case "AboutHome:Update":
this.onUpdate(aMessage.data);
break;
case "AboutHome:FocusInput":
this.onFocusInput();
break;
}
},
onUpdate: function(aData) {
let doc = content.document;
if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content))
doc.getElementById("launcher").setAttribute("session", "true");
// Inject search engine and snippets URL.
let docElt = doc.documentElement;
// set the following attributes BEFORE searchEngineName, which triggers to
// show the snippets when it's set.
docElt.setAttribute("snippetsURL", aData.snippetsURL);
if (aData.showKnowYourRights)
docElt.setAttribute("showKnowYourRights", "true");
docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
docElt.setAttribute("searchEngineName", aData.defaultEngineName);
},
onPageLoad: function() {
let doc = content.document;
if (doc.documentElement.hasAttribute("hasBrowserHandlers")) {
return;
}
doc.documentElement.setAttribute("hasBrowserHandlers", "true");
addMessageListener("AboutHome:Update", this);
addMessageListener("AboutHome:FocusInput", this);
addEventListener("click", this, true);
addEventListener("pagehide", this, true);
if (!Services.prefs.getBoolPref("browser.search.showOneOffButtons")) {
doc.documentElement.setAttribute("searchUIConfiguration", "oldsearchui");
}
sendAsyncMessage("AboutHome:RequestUpdate");
doc.addEventListener("AboutHomeSearchEvent", this, true, true);
doc.addEventListener("AboutHomeSearchPanel", this, true, true);
},
onClick: function(aEvent) {
if (!aEvent.isTrusted || // Don't trust synthetic events
aEvent.button == 2 || aEvent.target.localName != "button") {
return;
}
let originalTarget = aEvent.originalTarget;
let ownerDoc = originalTarget.ownerDocument;
if (ownerDoc.documentURI != "about:home") {
// This shouldn't happen, but we're being defensive.
return;
}
let elmId = originalTarget.getAttribute("id");
switch (elmId) {
case "restorePreviousSession":
sendAsyncMessage("AboutHome:RestorePreviousSession");
ownerDoc.getElementById("launcher").removeAttribute("session");
break;
case "downloads":
sendAsyncMessage("AboutHome:Downloads");
break;
case "bookmarks":
sendAsyncMessage("AboutHome:Bookmarks");
break;
case "history":
sendAsyncMessage("AboutHome:History");
break;
case "apps":
sendAsyncMessage("AboutHome:Apps");
break;
case "addons":
sendAsyncMessage("AboutHome:Addons");
break;
case "sync":
sendAsyncMessage("AboutHome:Sync");
break;
case "settings":
sendAsyncMessage("AboutHome:Settings");
break;
case "searchIcon":
sendAsyncMessage("AboutHome:OpenSearchPanel", null, { anchor: originalTarget });
break;
}
},
onPageHide: function(aEvent) {
if (aEvent.target.defaultView.frameElement) {
return;
}
removeMessageListener("AboutHome:Update", this);
removeEventListener("click", this, true);
removeEventListener("pagehide", this, true);
if (aEvent.target.documentElement) {
aEvent.target.documentElement.removeAttribute("hasBrowserHandlers");
}
},
onSearch: function(aEvent) {
sendAsyncMessage("AboutHome:Search", { searchData: aEvent.detail });
},
onOpenSearchPanel: function(aEvent) {
sendAsyncMessage("AboutHome:OpenSearchPanel");
},
onFocusInput: function () {
let searchInput = content.document.getElementById("searchText");
if (searchInput) {
searchInput.focus();
}
},
};
AboutHomeListener.init(this);
let AboutReaderListener = {
_articlePromise: null,
init: function() {
addEventListener("AboutReaderContentLoaded", this, false, true);
addEventListener("DOMContentLoaded", this, false);
addEventListener("pageshow", this, false);
addEventListener("pagehide", this, false);
addMessageListener("Reader:ParseDocument", this);
addMessageListener("Reader:PushState", this);
},
receiveMessage: function(message) {
switch (message.name) {
case "Reader:ParseDocument":
this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
content.document.location = "about:reader?url=" + encodeURIComponent(message.data.url);
break;
case "Reader:PushState":
this.updateReaderButton();
break;
}
},
get isAboutReader() {
return content.document.documentURI.startsWith("about:reader");
},
handleEvent: function(aEvent) {
if (aEvent.originalTarget.defaultView != content) {
return;
}
switch (aEvent.type) {
case "AboutReaderContentLoaded":
if (!this.isAboutReader) {
return;
}
if (content.document.body) {
// Update the toolbar icon to show the "reader active" icon.
sendAsyncMessage("Reader:UpdateReaderButton");
new AboutReader(global, content, this._articlePromise);
this._articlePromise = null;
}
break;
case "pagehide":
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
break;
case "pageshow":
// If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
// event, so we need to rely on "pageshow" in this case.
if (aEvent.persisted) {
this.updateReaderButton();
}
break;
case "DOMContentLoaded":
this.updateReaderButton();
break;
}
},
updateReaderButton: function() {
if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
!(content.document instanceof content.HTMLDocument) ||
content.document.mozSyntheticDocument) {
return;
}
// Only send updates when there are articles; there's no point updating with
// |false| all the time.
if (ReaderMode.isProbablyReaderable(content.document)) {
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
}
},
};
AboutReaderListener.init();
// An event listener for custom "WebChannelMessageToChrome" events on pages
addEventListener("WebChannelMessageToChrome", function (e) {
// if target is window then we want the document principal, otherwise fallback to target itself.
@ -613,68 +291,6 @@ addMessageListener("WebChannelMessageToContent", function (e) {
});
let ContentSearchMediator = {
whitelist: new Set([
"about:home",
"about:newtab",
]),
init: function (chromeGlobal) {
chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
addMessageListener("ContentSearch", this);
},
handleEvent: function (event) {
if (this._contentWhitelisted) {
this._sendMsg(event.detail.type, event.detail.data);
}
},
receiveMessage: function (msg) {
if (msg.data.type == "AddToWhitelist") {
for (let uri of msg.data.data) {
this.whitelist.add(uri);
}
this._sendMsg("AddToWhitelistAck");
return;
}
if (this._contentWhitelisted) {
this._fireEvent(msg.data.type, msg.data.data);
}
},
get _contentWhitelisted() {
return this.whitelist.has(content.document.documentURI);
},
_sendMsg: function (type, data=null) {
sendAsyncMessage("ContentSearch", {
type: type,
data: data,
});
},
_fireEvent: function (type, data=null) {
let event = Cu.cloneInto({
detail: {
type: type,
data: data,
},
}, content);
content.dispatchEvent(new content.CustomEvent("ContentSearchService",
event));
},
};
ContentSearchMediator.init(this);
// Lazily load the finder code
addMessageListener("Finder:Initialize", function () {
let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
new RemoteFinderListener(global);
});
let ClickEventHandler = {
init: function init() {
Cc["@mozilla.org/eventlistenerservice;1"]
@ -838,146 +454,6 @@ addEventListener("DOMWebNotificationClicked", function(event) {
sendAsyncMessage("DOMWebNotificationClicked", {});
}, false);
let PageStyleHandler = {
init: function() {
addMessageListener("PageStyle:Switch", this);
addMessageListener("PageStyle:Disable", this);
// Send a CPOW to the parent so that it can synchronously request
// the list of style sheets.
sendSyncMessage("PageStyle:SetSyncHandler", {}, {syncHandler: this});
},
get markupDocumentViewer() {
return docShell.contentViewer;
},
// Called synchronously via CPOW from the parent.
getStyleSheetInfo: function() {
let styleSheets = this._filterStyleSheets(this.getAllStyleSheets());
return {
styleSheets: styleSheets,
authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
preferredStyleSheetSet: content.document.preferredStyleSheetSet
};
},
// Called synchronously via CPOW from the parent.
getAllStyleSheets: function(frameset = content) {
let selfSheets = Array.slice(frameset.document.styleSheets);
let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
return selfSheets.concat(...subSheets);
},
receiveMessage: function(msg) {
switch (msg.name) {
case "PageStyle:Switch":
this.markupDocumentViewer.authorStyleDisabled = false;
this._stylesheetSwitchAll(content, msg.data.title);
break;
case "PageStyle:Disable":
this.markupDocumentViewer.authorStyleDisabled = true;
break;
}
},
_stylesheetSwitchAll: function (frameset, title) {
if (!title || this._stylesheetInFrame(frameset, title)) {
this._stylesheetSwitchFrame(frameset, title);
}
for (let i = 0; i < frameset.frames.length; i++) {
// Recurse into sub-frames.
this._stylesheetSwitchAll(frameset.frames[i], title);
}
},
_stylesheetSwitchFrame: function (frame, title) {
var docStyleSheets = frame.document.styleSheets;
for (let i = 0; i < docStyleSheets.length; ++i) {
let docStyleSheet = docStyleSheets[i];
if (docStyleSheet.title) {
docStyleSheet.disabled = (docStyleSheet.title != title);
} else if (docStyleSheet.disabled) {
docStyleSheet.disabled = false;
}
}
},
_stylesheetInFrame: function (frame, title) {
return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
},
_filterStyleSheets: function(styleSheets) {
let result = [];
for (let currentStyleSheet of styleSheets) {
if (!currentStyleSheet.title)
continue;
// Skip any stylesheets that don't match the screen media type.
if (currentStyleSheet.media.length > 0) {
let mediaQueryList = currentStyleSheet.media.mediaText;
if (!content.matchMedia(mediaQueryList).matches) {
continue;
}
}
result.push({title: currentStyleSheet.title,
disabled: currentStyleSheet.disabled});
}
return result;
},
};
PageStyleHandler.init();
// Keep a reference to the translation content handler to avoid it it being GC'ed.
let trHandler = null;
if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
trHandler = new TranslationContentHandler(global, docShell);
}
let DOMFullscreenHandler = {
_fullscreenDoc: null,
init: function() {
addMessageListener("DOMFullscreen:Approved", this);
addMessageListener("DOMFullscreen:CleanUp", this);
addEventListener("MozEnteredDomFullscreen", this);
},
receiveMessage: function(aMessage) {
switch(aMessage.name) {
case "DOMFullscreen:Approved": {
if (this._fullscreenDoc) {
Services.obs.notifyObservers(this._fullscreenDoc,
"fullscreen-approved",
"");
}
break;
}
case "DOMFullscreen:CleanUp": {
this._fullscreenDoc = null;
break;
}
}
},
handleEvent: function(aEvent) {
if (aEvent.type == "MozEnteredDomFullscreen") {
this._fullscreenDoc = aEvent.target;
sendAsyncMessage("MozEnteredDomFullscreen", {
origin: this._fullscreenDoc.nodePrincipal.origin,
});
}
}
};
DOMFullscreenHandler.init();
ContentWebRTC.init();
addMessageListener("webrtc:Allow", ContentWebRTC);
addMessageListener("webrtc:Deny", ContentWebRTC);
@ -990,59 +466,6 @@ addMessageListener("webrtc:StartBrowserSharing", () => {
});
});
function gKeywordURIFixup(fixupInfo) {
fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
// Ignore info from other docshells
let parent = fixupInfo.consumer.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeRootTreeItem;
if (parent != docShell)
return;
let data = {};
for (let f of Object.keys(fixupInfo)) {
if (f == "consumer" || typeof fixupInfo[f] == "function")
continue;
if (fixupInfo[f] && fixupInfo[f] instanceof Ci.nsIURI) {
data[f] = fixupInfo[f].spec;
} else {
data[f] = fixupInfo[f];
}
}
sendAsyncMessage("Browser:URIFixup", data);
}
Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup", false);
addEventListener("unload", () => {
Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
}, false);
addMessageListener("Browser:AppTab", function(message) {
docShell.isAppTab = message.data.isAppTab;
});
let WebBrowserChrome = {
onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
},
// Check whether this URI should load in the current process
shouldLoadURI: function(aDocShell, aURI, aReferrer) {
if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
return false;
}
return true;
},
};
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsITabChild);
tabchild.webBrowserChrome = WebBrowserChrome;
}
addEventListener("pageshow", function(event) {
if (event.target == content.document) {
sendAsyncMessage("PageVisibility:Show", {
@ -1170,3 +593,9 @@ addMessageListener("ContextMenu:Canvas:ToDataURL", (message) => {
addMessageListener("ContextMenu:ReloadFrame", (message) => {
message.objects.target.ownerDocument.location.reload();
});
addMessageListener("ContextMenu:ReloadImage", (message) => {
let image = message.objects.target;
if (image instanceof Ci.nsIImageLoadingContent)
image.forceReload();
});

View File

@ -600,6 +600,7 @@ nsContextMenu.prototype = {
this.focusedElement = elt;
let ownerDoc = this.target.ownerDocument;
this.ownerDoc = ownerDoc;
// If this is a remote context menu event, use the information from
// gContextMenuContentData instead.
@ -1027,13 +1028,13 @@ nsContextMenu.prototype = {
BrowserPageInfo(this.target.ownerDocument);
},
reloadImage: function(e) {
reloadImage: function() {
urlSecurityCheck(this.mediaURL,
this.browser.contentPrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
if (this.target instanceof Ci.nsIImageLoadingContent)
this.target.forceReload();
this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadImage",
null, { target: this.target });
},
_canvasToDataURL: function(target) {
@ -1311,9 +1312,8 @@ nsContextMenu.prototype = {
// Save URL of clicked-on link.
saveLink: function() {
var doc = this.target.ownerDocument;
urlSecurityCheck(this.linkURL, this.principal);
this.saveHelper(this.linkURL, this.linkText, null, true, doc);
this.saveHelper(this.linkURL, this.linkText, null, true, this.ownerDoc);
},
// Backwards-compatibility wrapper

View File

@ -0,0 +1,596 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* This content script contains code that requires a tab browser. */
let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
"resource:///modules/E10SUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AboutReader",
"resource://gre/modules/AboutReader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
XPCOMUtils.defineLazyGetter(this, "SimpleServiceDiscovery", function() {
let ssdp = Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm", {}).SimpleServiceDiscovery;
// Register targets
ssdp.registerDevice({
id: "roku:ecp",
target: "roku:ecp",
factory: function(aService) {
Cu.import("resource://gre/modules/RokuApp.jsm");
return new RokuApp(aService);
},
mirror: true,
types: ["video/mp4"],
extensions: ["mp4"]
});
return ssdp;
});
// TabChildGlobal
var global = this;
addMessageListener("Browser:HideSessionRestoreButton", function (message) {
// Hide session restore button on about:home
let doc = content.document;
let container;
if (doc.documentURI.toLowerCase() == "about:home" &&
(container = doc.getElementById("sessionRestoreContainer"))) {
container.hidden = true;
}
});
addMessageListener("Browser:Reload", function(message) {
/* First, we'll try to use the session history object to reload so
* that framesets are handled properly. If we're in a special
* window (such as view-source) that has no session history, fall
* back on using the web navigation's reload method.
*/
let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
try {
let sh = webNav.sessionHistory;
if (sh)
webNav = sh.QueryInterface(Ci.nsIWebNavigation);
} catch (e) {
}
let reloadFlags = message.data.flags;
let handlingUserInput;
try {
handlingUserInput = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.setHandlingUserInput(message.data.handlingUserInput);
webNav.reload(reloadFlags);
} catch (e) {
} finally {
handlingUserInput.destruct();
}
});
addMessageListener("MixedContent:ReenableProtection", function() {
docShell.mixedContentChannel = null;
});
addMessageListener("SecondScreen:tab-mirror", function(message) {
if (!Services.prefs.getBoolPref("browser.casting.enabled")) {
return;
}
let app = SimpleServiceDiscovery.findAppForService(message.data.service);
if (app) {
let width = content.innerWidth;
let height = content.innerHeight;
let viewport = {cssWidth: width, cssHeight: height, width: width, height: height};
app.mirror(function() {}, content, viewport, function() {}, content);
}
});
let AboutHomeListener = {
init: function(chromeGlobal) {
chromeGlobal.addEventListener('AboutHomeLoad', this, false, true);
},
get isAboutHome() {
return content.document.documentURI.toLowerCase() == "about:home";
},
handleEvent: function(aEvent) {
if (!this.isAboutHome) {
return;
}
switch (aEvent.type) {
case "AboutHomeLoad":
this.onPageLoad();
break;
case "AboutHomeSearchEvent":
this.onSearch(aEvent);
break;
case "AboutHomeSearchPanel":
this.onOpenSearchPanel(aEvent);
break;
case "click":
this.onClick(aEvent);
break;
case "pagehide":
this.onPageHide(aEvent);
break;
}
},
receiveMessage: function(aMessage) {
if (!this.isAboutHome) {
return;
}
switch (aMessage.name) {
case "AboutHome:Update":
this.onUpdate(aMessage.data);
break;
case "AboutHome:FocusInput":
this.onFocusInput();
break;
}
},
onUpdate: function(aData) {
let doc = content.document;
if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content))
doc.getElementById("launcher").setAttribute("session", "true");
// Inject search engine and snippets URL.
let docElt = doc.documentElement;
// set the following attributes BEFORE searchEngineName, which triggers to
// show the snippets when it's set.
docElt.setAttribute("snippetsURL", aData.snippetsURL);
if (aData.showKnowYourRights)
docElt.setAttribute("showKnowYourRights", "true");
docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
docElt.setAttribute("searchEngineName", aData.defaultEngineName);
},
onPageLoad: function() {
let doc = content.document;
if (doc.documentElement.hasAttribute("hasBrowserHandlers")) {
return;
}
doc.documentElement.setAttribute("hasBrowserHandlers", "true");
addMessageListener("AboutHome:Update", this);
addMessageListener("AboutHome:FocusInput", this);
addEventListener("click", this, true);
addEventListener("pagehide", this, true);
if (!Services.prefs.getBoolPref("browser.search.showOneOffButtons")) {
doc.documentElement.setAttribute("searchUIConfiguration", "oldsearchui");
}
sendAsyncMessage("AboutHome:RequestUpdate");
doc.addEventListener("AboutHomeSearchEvent", this, true, true);
doc.addEventListener("AboutHomeSearchPanel", this, true, true);
},
onClick: function(aEvent) {
if (!aEvent.isTrusted || // Don't trust synthetic events
aEvent.button == 2 || aEvent.target.localName != "button") {
return;
}
let originalTarget = aEvent.originalTarget;
let ownerDoc = originalTarget.ownerDocument;
if (ownerDoc.documentURI != "about:home") {
// This shouldn't happen, but we're being defensive.
return;
}
let elmId = originalTarget.getAttribute("id");
switch (elmId) {
case "restorePreviousSession":
sendAsyncMessage("AboutHome:RestorePreviousSession");
ownerDoc.getElementById("launcher").removeAttribute("session");
break;
case "downloads":
sendAsyncMessage("AboutHome:Downloads");
break;
case "bookmarks":
sendAsyncMessage("AboutHome:Bookmarks");
break;
case "history":
sendAsyncMessage("AboutHome:History");
break;
case "apps":
sendAsyncMessage("AboutHome:Apps");
break;
case "addons":
sendAsyncMessage("AboutHome:Addons");
break;
case "sync":
sendAsyncMessage("AboutHome:Sync");
break;
case "settings":
sendAsyncMessage("AboutHome:Settings");
break;
case "searchIcon":
sendAsyncMessage("AboutHome:OpenSearchPanel", null, { anchor: originalTarget });
break;
}
},
onPageHide: function(aEvent) {
if (aEvent.target.defaultView.frameElement) {
return;
}
removeMessageListener("AboutHome:Update", this);
removeEventListener("click", this, true);
removeEventListener("pagehide", this, true);
if (aEvent.target.documentElement) {
aEvent.target.documentElement.removeAttribute("hasBrowserHandlers");
}
},
onSearch: function(aEvent) {
sendAsyncMessage("AboutHome:Search", { searchData: aEvent.detail });
},
onOpenSearchPanel: function(aEvent) {
sendAsyncMessage("AboutHome:OpenSearchPanel");
},
onFocusInput: function () {
let searchInput = content.document.getElementById("searchText");
if (searchInput) {
searchInput.focus();
}
},
};
AboutHomeListener.init(this);
let AboutReaderListener = {
_articlePromise: null,
init: function() {
addEventListener("AboutReaderContentLoaded", this, false, true);
addEventListener("DOMContentLoaded", this, false);
addEventListener("pageshow", this, false);
addEventListener("pagehide", this, false);
addMessageListener("Reader:ParseDocument", this);
addMessageListener("Reader:PushState", this);
},
receiveMessage: function(message) {
switch (message.name) {
case "Reader:ParseDocument":
this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
content.document.location = "about:reader?url=" + encodeURIComponent(message.data.url);
break;
case "Reader:PushState":
this.updateReaderButton();
break;
}
},
get isAboutReader() {
return content.document.documentURI.startsWith("about:reader");
},
handleEvent: function(aEvent) {
if (aEvent.originalTarget.defaultView != content) {
return;
}
switch (aEvent.type) {
case "AboutReaderContentLoaded":
if (!this.isAboutReader) {
return;
}
if (content.document.body) {
// Update the toolbar icon to show the "reader active" icon.
sendAsyncMessage("Reader:UpdateReaderButton");
new AboutReader(global, content, this._articlePromise);
this._articlePromise = null;
}
break;
case "pagehide":
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
break;
case "pageshow":
// If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
// event, so we need to rely on "pageshow" in this case.
if (aEvent.persisted) {
this.updateReaderButton();
}
break;
case "DOMContentLoaded":
this.updateReaderButton();
break;
}
},
updateReaderButton: function() {
if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
!(content.document instanceof content.HTMLDocument) ||
content.document.mozSyntheticDocument) {
return;
}
// Only send updates when there are articles; there's no point updating with
// |false| all the time.
if (ReaderMode.isProbablyReaderable(content.document)) {
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
}
},
};
AboutReaderListener.init();
let ContentSearchMediator = {
whitelist: new Set([
"about:home",
"about:newtab",
]),
init: function (chromeGlobal) {
chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
addMessageListener("ContentSearch", this);
},
handleEvent: function (event) {
if (this._contentWhitelisted) {
this._sendMsg(event.detail.type, event.detail.data);
}
},
receiveMessage: function (msg) {
if (msg.data.type == "AddToWhitelist") {
for (let uri of msg.data.data) {
this.whitelist.add(uri);
}
this._sendMsg("AddToWhitelistAck");
return;
}
if (this._contentWhitelisted) {
this._fireEvent(msg.data.type, msg.data.data);
}
},
get _contentWhitelisted() {
return this.whitelist.has(content.document.documentURI);
},
_sendMsg: function (type, data=null) {
sendAsyncMessage("ContentSearch", {
type: type,
data: data,
});
},
_fireEvent: function (type, data=null) {
let event = Cu.cloneInto({
detail: {
type: type,
data: data,
},
}, content);
content.dispatchEvent(new content.CustomEvent("ContentSearchService",
event));
},
};
ContentSearchMediator.init(this);
let PageStyleHandler = {
init: function() {
addMessageListener("PageStyle:Switch", this);
addMessageListener("PageStyle:Disable", this);
// Send a CPOW to the parent so that it can synchronously request
// the list of style sheets.
sendSyncMessage("PageStyle:SetSyncHandler", {}, {syncHandler: this});
},
get markupDocumentViewer() {
return docShell.contentViewer;
},
// Called synchronously via CPOW from the parent.
getStyleSheetInfo: function() {
let styleSheets = this._filterStyleSheets(this.getAllStyleSheets());
return {
styleSheets: styleSheets,
authorStyleDisabled: this.markupDocumentViewer.authorStyleDisabled,
preferredStyleSheetSet: content.document.preferredStyleSheetSet
};
},
// Called synchronously via CPOW from the parent.
getAllStyleSheets: function(frameset = content) {
let selfSheets = Array.slice(frameset.document.styleSheets);
let subSheets = Array.map(frameset.frames, frame => this.getAllStyleSheets(frame));
return selfSheets.concat(...subSheets);
},
receiveMessage: function(msg) {
switch (msg.name) {
case "PageStyle:Switch":
this.markupDocumentViewer.authorStyleDisabled = false;
this._stylesheetSwitchAll(content, msg.data.title);
break;
case "PageStyle:Disable":
this.markupDocumentViewer.authorStyleDisabled = true;
break;
}
},
_stylesheetSwitchAll: function (frameset, title) {
if (!title || this._stylesheetInFrame(frameset, title)) {
this._stylesheetSwitchFrame(frameset, title);
}
for (let i = 0; i < frameset.frames.length; i++) {
// Recurse into sub-frames.
this._stylesheetSwitchAll(frameset.frames[i], title);
}
},
_stylesheetSwitchFrame: function (frame, title) {
var docStyleSheets = frame.document.styleSheets;
for (let i = 0; i < docStyleSheets.length; ++i) {
let docStyleSheet = docStyleSheets[i];
if (docStyleSheet.title) {
docStyleSheet.disabled = (docStyleSheet.title != title);
} else if (docStyleSheet.disabled) {
docStyleSheet.disabled = false;
}
}
},
_stylesheetInFrame: function (frame, title) {
return Array.some(frame.document.styleSheets, (styleSheet) => styleSheet.title == title);
},
_filterStyleSheets: function(styleSheets) {
let result = [];
for (let currentStyleSheet of styleSheets) {
if (!currentStyleSheet.title)
continue;
// Skip any stylesheets that don't match the screen media type.
if (currentStyleSheet.media.length > 0) {
let mediaQueryList = currentStyleSheet.media.mediaText;
if (!content.matchMedia(mediaQueryList).matches) {
continue;
}
}
result.push({title: currentStyleSheet.title,
disabled: currentStyleSheet.disabled});
}
return result;
},
};
PageStyleHandler.init();
// Keep a reference to the translation content handler to avoid it it being GC'ed.
let trHandler = null;
if (Services.prefs.getBoolPref("browser.translation.detectLanguage")) {
Cu.import("resource:///modules/translation/TranslationContentHandler.jsm");
trHandler = new TranslationContentHandler(global, docShell);
}
function gKeywordURIFixup(fixupInfo) {
fixupInfo.QueryInterface(Ci.nsIURIFixupInfo);
// Ignore info from other docshells
let parent = fixupInfo.consumer.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeRootTreeItem;
if (parent != docShell)
return;
let data = {};
for (let f of Object.keys(fixupInfo)) {
if (f == "consumer" || typeof fixupInfo[f] == "function")
continue;
if (fixupInfo[f] && fixupInfo[f] instanceof Ci.nsIURI) {
data[f] = fixupInfo[f].spec;
} else {
data[f] = fixupInfo[f];
}
}
sendAsyncMessage("Browser:URIFixup", data);
}
Services.obs.addObserver(gKeywordURIFixup, "keyword-uri-fixup", false);
addEventListener("unload", () => {
Services.obs.removeObserver(gKeywordURIFixup, "keyword-uri-fixup");
}, false);
addMessageListener("Browser:AppTab", function(message) {
docShell.isAppTab = message.data.isAppTab;
});
let WebBrowserChrome = {
onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
},
// Check whether this URI should load in the current process
shouldLoadURI: function(aDocShell, aURI, aReferrer) {
if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
E10SUtils.redirectLoad(aDocShell, aURI, aReferrer);
return false;
}
return true;
},
};
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsITabChild);
tabchild.webBrowserChrome = WebBrowserChrome;
}
let DOMFullscreenHandler = {
_fullscreenDoc: null,
init: function() {
addMessageListener("DOMFullscreen:Approved", this);
addMessageListener("DOMFullscreen:CleanUp", this);
addEventListener("MozEnteredDomFullscreen", this);
},
receiveMessage: function(aMessage) {
switch(aMessage.name) {
case "DOMFullscreen:Approved": {
if (this._fullscreenDoc) {
Services.obs.notifyObservers(this._fullscreenDoc,
"fullscreen-approved",
"");
}
break;
}
case "DOMFullscreen:CleanUp": {
this._fullscreenDoc = null;
break;
}
}
},
handleEvent: function(aEvent) {
if (aEvent.type == "MozEnteredDomFullscreen") {
this._fullscreenDoc = aEvent.target;
sendAsyncMessage("MozEnteredDomFullscreen", {
origin: this._fullscreenDoc.nodePrincipal.origin,
});
}
}
};
DOMFullscreenHandler.init();

View File

@ -77,6 +77,7 @@ browser.jar:
* content/browser/browser.xul (content/browser.xul)
* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
* content/browser/chatWindow.xul (content/chatWindow.xul)
content/browser/tab-content.js (content/tab-content.js)
content/browser/content.js (content/content.js)
content/browser/defaultthemes/1.footer.jpg (content/defaultthemes/1.footer.jpg)
content/browser/defaultthemes/1.header.jpg (content/defaultthemes/1.header.jpg)

View File

@ -128,11 +128,22 @@ ServerClient.prototype = {
}
request.onComplete = error => {
// Although the server API docs say the "Backoff" header is on
// successful responses while "Retry-After" is on error responses, we
// just look for them both in both cases (as the scheduler makes no
// distinction)
let response = request.response;
if (response && response.headers) {
let backoff = response.headers["backoff"] || response.headers["retry-after"];
if (backoff) {
log.info("Server requested backoff", backoff);
Services.obs.notifyObservers(null, "readinglist:backoff-requested", backoff);
}
}
if (error) {
return reject(this._convertRestError(error));
}
let response = request.response;
log.debug("received response status: ${status} ${statusText}", response);
// Handle response status codes we know about
let result = {

View File

@ -81,6 +81,16 @@ function OAuthTokenServer() {
return server;
}
function promiseObserver(topic) {
return new Promise(resolve => {
function observe(subject, topic, data) {
Services.obs.removeObserver(observe, topic);
resolve(data);
}
Services.obs.addObserver(observe, topic, false);
});
}
// The tests.
function run_test() {
run_next_test();
@ -165,6 +175,72 @@ add_task(function testHeaders() {
}
});
// Check that a "backoff" header causes the correct notification.
add_task(function testBackoffHeader() {
let handlers = {
"/v1/batch": (request, response) => {
response.setHeader("Backoff", "123");
response.setStatusLine("1.1", 200, "OK");
response.write("{}");
}
};
let rlserver = new Server(handlers);
rlserver.start();
let observerPromise = promiseObserver("readinglist:backoff-requested");
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
sc._getToken = () => Promise.resolve();
let response = yield sc.request({
path: "/batch",
method: "post",
headers: {"X-Foo": "bar"},
body: {foo: "bar"}});
equal(response.status, 200, "got the 200 we expected");
let data = yield observerPromise;
equal(data, "123", "got the expected header value.")
} finally {
yield rlserver.stop();
}
});
// Check that a "backoff" header causes the correct notification.
add_task(function testRetryAfterHeader() {
let handlers = {
"/v1/batch": (request, response) => {
response.setHeader("Retry-After", "456");
response.setStatusLine("1.1", 500, "Not OK");
response.write("{}");
}
};
let rlserver = new Server(handlers);
rlserver.start();
let observerPromise = promiseObserver("readinglist:backoff-requested");
try {
Services.prefs.setCharPref("readinglist.server", rlserver.host + "/v1");
let fxa = yield createMockFxA();
let sc = new ServerClient(fxa);
sc._getToken = () => Promise.resolve();
let response = yield sc.request({
path: "/batch",
method: "post",
headers: {"X-Foo": "bar"},
body: {foo: "bar"}});
equal(response.status, 500, "got the 500 we expected");
let data = yield observerPromise;
equal(data, "456", "got the expected header value.")
} finally {
yield rlserver.stop();
}
});
// Check that unicode ends up as utf-8 in requests, and vice-versa in responses.
// (Note the ServerClient assumes all strings in and out are UCS, and thus have
// already been encoded/decoded (ie, it never expects to receive stuff already

View File

@ -75,6 +75,8 @@ function StyleEditorUI(debuggee, target, panelDoc) {
this._updateMediaList = this._updateMediaList.bind(this);
this._clear = this._clear.bind(this);
this._onError = this._onError.bind(this);
this._updateOpenLinkItem = this._updateOpenLinkItem.bind(this);
this._openLinkNewTab = this._openLinkNewTab.bind(this);
this._prefObserver = new PrefObserver("devtools.styleeditor.");
this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument);
@ -164,6 +166,14 @@ StyleEditorUI.prototype = {
});
this._optionsButton = this._panelDoc.getElementById("style-editor-options");
this._panelDoc.addEventListener("contextmenu", () => {
this._contextMenuStyleSheet = null;
}, true);
this._contextMenu = this._panelDoc.getElementById("sidebar-context");
this._contextMenu.addEventListener("popupshowing",
this._updateOpenLinkItem);
this._optionsMenu = this._panelDoc.getElementById("style-editor-options-popup");
this._optionsMenu.addEventListener("popupshowing",
this._onOptionsPopupShowing);
@ -173,10 +183,15 @@ StyleEditorUI.prototype = {
this._sourcesItem = this._panelDoc.getElementById("options-origsources");
this._sourcesItem.addEventListener("command",
this._toggleOrigSources);
this._mediaItem = this._panelDoc.getElementById("options-show-media");
this._mediaItem.addEventListener("command",
this._toggleMediaSidebar);
this._openLinkNewTabItem = this._panelDoc.getElementById("context-openlinknewtab");
this._openLinkNewTabItem.addEventListener("command",
this._openLinkNewTab);
let nav = this._panelDoc.querySelector(".splitview-controller");
nav.setAttribute("width", Services.prefs.getIntPref(PREF_NAV_WIDTH));
},
@ -411,6 +426,29 @@ StyleEditorUI.prototype = {
this.editors.forEach(this._updateMediaList);
},
/**
* This method handles the following cases related to the context menu item "_openLinkNewTabItem":
*
* 1) There was a stylesheet clicked on and it is external: show and enable the context menu item
* 2) There was a stylesheet clicked on and it is inline: show and disable the context menu item
* 3) There was no stylesheet clicked on (the right click happened below the list): hide the context menu
*/
_updateOpenLinkItem: function() {
this._openLinkNewTabItem.setAttribute("hidden", !this._contextMenuStyleSheet);
if (this._contextMenuStyleSheet) {
this._openLinkNewTabItem.setAttribute("disabled", !this._contextMenuStyleSheet.href);
}
},
/**
* Open a particular stylesheet in a new tab.
*/
_openLinkNewTab: function() {
if (this._contextMenuStyleSheet) {
this._window.openUILinkIn(this._contextMenuStyleSheet.href, "tab");
}
},
/**
* Remove a particular stylesheet editor from the UI
*
@ -493,6 +531,10 @@ StyleEditorUI.prototype = {
this._updateSummaryForEditor(editor, summary);
summary.addEventListener("contextmenu", (event) => {
this._contextMenuStyleSheet = editor.styleSheet;
}, false);
summary.addEventListener("focus", function onSummaryFocus(event) {
if (event.target == summary) {
// autofocus the stylesheet name

View File

@ -30,6 +30,7 @@
<script type="application/javascript;version=1.8"
src="chrome://browser/content/devtools/theme-switching.js"/>
<xul:script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<xul:script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
<xul:script type="application/javascript">
function goUpdateSourceEditorMenuItems() {
goUpdateGlobalEditMenuItems();
@ -61,6 +62,10 @@
key="key_gotoLine"
command="cmd_gotoLine"/>
</xul:menupopup>
<xul:menupopup id="sidebar-context">
<xul:menuitem id="context-openlinknewtab"
label="&openLinkNewTab.label;"/>
</xul:menupopup>
<xul:menupopup id="style-editor-options-popup"
position="before_start">
<xul:menuitem id="options-origsources"

View File

@ -68,6 +68,7 @@ skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
[browser_styleeditor_navigate.js]
[browser_styleeditor_new.js]
[browser_styleeditor_nostyle.js]
[browser_styleeditor_opentab.js]
[browser_styleeditor_pretty.js]
[browser_styleeditor_private_perwindowpb.js]
[browser_styleeditor_reload.js]

View File

@ -21,8 +21,8 @@ add_task(function() {
info("Opening Style Editor");
let styleeditor = yield toolbox.selectTool("styleeditor");
info("Waiting for an editor to be selected.");
yield styleeditor.UI.once("editor-selected");
info("Waiting for the source to be loaded.");
yield styleeditor.UI.editors[0].getSourceEditor();
info("Checking Netmonitor contents.");
let requestsForCss = 0;

View File

@ -0,0 +1,115 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// A test to check the 'Open Link in new tab' functionality in the
// context menu item for stylesheets (bug 992947).
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
waitForExplicitFinish();
add_task(function*() {
let panel = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
let ui = panel.UI;
yield rightClickStyleSheet(ui, ui.editors[0]);
is(ui._openLinkNewTabItem.getAttribute("disabled"), "false", "The menu item is not disabled");
is(ui._openLinkNewTabItem.getAttribute("hidden"), "false", "The menu item is not hidden");
let url = "https://example.com/browser/browser/devtools/styleeditor/test/simple.css";
is(ui._contextMenuStyleSheet.href, url, "Correct URL for sheet");
let originalOpenUILinkIn = ui._window.openUILinkIn;
let tabOpenedDefer = promise.defer();
ui._window.openUILinkIn = newUrl => {
// Reset the actual openUILinkIn function before proceeding.
ui._window.openUILinkIn = originalOpenUILinkIn;
is (newUrl, url, "The correct tab has been opened");
tabOpenedDefer.resolve();
};
ui._openLinkNewTabItem.click();
info (`Waiting for a tab to open - ${url}`);
yield tabOpenedDefer.promise;
yield rightClickInlineStyleSheet(ui, ui.editors[1]);
is(ui._openLinkNewTabItem.getAttribute("disabled"), "true", "The menu item is disabled");
is(ui._openLinkNewTabItem.getAttribute("hidden"), "false", "The menu item is not hidden");
yield rightClickNoStyleSheet(ui);
is(ui._openLinkNewTabItem.getAttribute("hidden"), "true", "The menu item is not hidden");
});
function onPopupShow(contextMenu) {
let defer = promise.defer();
contextMenu.addEventListener("popupshown", function onpopupshown() {
contextMenu.removeEventListener("popupshown", onpopupshown);
defer.resolve();
});
return defer.promise;
}
function onPopupHide(contextMenu) {
let defer = promise.defer();
contextMenu.addEventListener("popuphidden", function popuphidden() {
contextMenu.removeEventListener("popuphidden", popuphidden);
defer.resolve();
});
return defer.promise;
}
function rightClickStyleSheet(ui, editor) {
let defer = promise.defer();
onPopupShow(ui._contextMenu).then(()=> {
onPopupHide(ui._contextMenu).then(() => {
defer.resolve();
});
ui._contextMenu.hidePopup();
});
EventUtils.synthesizeMouseAtCenter(
editor.summary.querySelector(".stylesheet-name"),
{button: 2, type: "contextmenu"},
gPanelWindow);
return defer.promise;
}
function rightClickInlineStyleSheet(ui, editor) {
let defer = promise.defer();
onPopupShow(ui._contextMenu).then(()=> {
onPopupHide(ui._contextMenu).then(() => {
defer.resolve();
});
ui._contextMenu.hidePopup();
});
EventUtils.synthesizeMouseAtCenter(
editor.summary.querySelector(".stylesheet-name"),
{button: 2, type: "contextmenu"},
gPanelWindow);
return defer.promise;
}
function rightClickNoStyleSheet(ui) {
let defer = promise.defer();
onPopupShow(ui._contextMenu).then(()=> {
onPopupHide(ui._contextMenu).then(() => {
defer.resolve();
});
ui._contextMenu.hidePopup();
});
EventUtils.synthesizeMouseAtCenter(
ui._panelDoc.querySelector("#splitview-tpl-summary-stylesheet"),
{button: 2, type: "contextmenu"},
gPanelWindow);
return defer.promise;
}

View File

@ -1,6 +1,7 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>simple testcase</title>
<link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/>
<style type="text/css">

View File

@ -61,3 +61,7 @@
<!ENTITY noStyleSheet-tip-action.label "append a new style sheet">
<!-- LOCALICATION NOTE (noStyleSheet-tip-end.label): End of the tip sentence -->
<!ENTITY noStyleSheet-tip-end.label "?">
<!-- LOCALIZATION NOTE (openLinkNewTab.label): This is the text for the
context menu item that opens a stylesheet in a new tab -->
<!ENTITY openLinkNewTab.label "Open Link In New Tab">

View File

@ -214,6 +214,9 @@ let ReaderParent = {
* @resolves JS object representing the article, or null if no article is found.
*/
_getArticle: Task.async(function* (url, browser) {
return yield ReaderMode.downloadAndParseDocument(url);
return yield ReaderMode.downloadAndParseDocument(url).catch(e => {
Cu.reportError("Error downloading and parsing document: " + e);
return null;
});
})
};

View File

@ -919,6 +919,15 @@ bool DefineOSFileConstants(JSContext *cx, JS::Handle<JSObject*> global)
}
#endif
#if defined(HAVE_64BIT_BUILD)
JS::Rooted<JS::Value> valBits(cx, INT_TO_JSVAL(64));
#else
JS::Rooted<JS::Value> valBits(cx, INT_TO_JSVAL(32));
#endif //defined (HAVE_64BIT_BUILD)
if (!JS_SetProperty(cx, objSys, "bits", valBits)) {
return false;
}
dom::ConstantSpec umask_cs[] = {
{ "umask", UINT_TO_JSVAL(gUserUmask) },
PROP_END

View File

@ -19,6 +19,7 @@ self.onmessage = function(msg) {
test_xul();
test_debugBuildWorkerThread(isDebugBuild);
test_umaskWorkerThread(umask);
test_bits();
} catch (x) {
log("Catching error: " + x);
log("Stack: " + x.stack);
@ -74,3 +75,8 @@ function test_xul() {
}
ok(true, "test_xul: opened libxul successfully");
}
// Check if the value of OS.Constants.Sys.bits is 32 or 64
function test_bits(){
is(OS.Constants.Sys.bits, ctypes.int.ptr.size * 8, "OS.Constants.Sys.bits is either 32 or 64");
}

View File

@ -71,6 +71,7 @@ NS_IMPL_ISUPPORTS(SelectionCarets,
nsISupportsWeakReference)
/*static*/ int32_t SelectionCarets::sSelectionCaretsInflateSize = 0;
/*static*/ bool SelectionCarets::sSelectionCaretDetectsLongTap = true;
SelectionCarets::SelectionCarets(nsIPresShell* aPresShell)
: mPresShell(aPresShell)
@ -98,6 +99,8 @@ SelectionCarets::SelectionCarets(nsIPresShell* aPresShell)
if (!addedPref) {
Preferences::AddIntVarCache(&sSelectionCaretsInflateSize,
"selectioncaret.inflatesize.threshold");
Preferences::AddBoolVarCache(&sSelectionCaretDetectsLongTap,
"selectioncaret.detects.longtap", true);
addedPref = true;
}
}
@ -267,13 +270,16 @@ SelectionCarets::HandleEvent(WidgetEvent* aEvent)
nsPresContext::AppUnitsPerCSSPixel() * kMoveStartTolerancePx) {
CancelLongTapDetector();
}
} else if (aEvent->message == NS_MOUSE_MOZLONGTAP) {
if (!mVisible) {
SELECTIONCARETS_LOG("SelectWord from APZ");
if (!mVisible || !sSelectionCaretDetectsLongTap) {
SELECTIONCARETS_LOG("SelectWord from NS_MOUSE_MOZLONGTAP");
mDownPoint = ptInRoot;
nsresult wordSelected = SelectWord();
if (NS_FAILED(wordSelected)) {
SELECTIONCARETS_LOG("SelectWord from APZ failed!")
SELECTIONCARETS_LOG("SelectWord from NS_MOUSE_MOZLONGTAP failed!");
return nsEventStatus_eIgnore;
}
@ -1218,7 +1224,7 @@ SelectionCarets::ScrollPositionChanged()
void
SelectionCarets::LaunchLongTapDetector()
{
if (mUseAsyncPanZoom) {
if (!sSelectionCaretDetectsLongTap || mUseAsyncPanZoom) {
return;
}

View File

@ -271,6 +271,7 @@ private:
// Preference
static int32_t sSelectionCaretsInflateSize;
static bool sSelectionCaretDetectsLongTap;
};
} // namespace mozilla

View File

@ -867,3 +867,6 @@ pref("browser.readinglist.enabled", true);
pref("gfx.vsync.hw-vsync.enabled", true);
pref("gfx.vsync.compositor", true);
pref("gfx.vsync.refreshdriver", true);
// Selection carets never fall-back to internal LongTap detector.
pref("selectioncaret.detects.longtap", false);

View File

@ -211,8 +211,29 @@ public class DBUtils {
@RobocopTarget
public enum UpdateOperation {
/**
* ASSIGN is the usual update: replaces the value in the named column with the provided value.
*
* foo = ?
*/
ASSIGN,
/**
* BITWISE_OR applies the provided value to the existing value with a bitwise OR. This is useful for adding to flags.
*
* foo |= ?
*/
BITWISE_OR,
/**
* EXPRESSION is an end-run around the API: it allows callers to specify a fragment of SQL to splice into the
* SET part of the query.
*
* foo = $value
*
* Be very careful not to use user input in this.
*/
EXPRESSION,
}
/**
@ -245,7 +266,10 @@ public class DBUtils {
// move all bind args to one array
int setValuesSize = 0;
for (int i = 0; i < values.length; i++) {
setValuesSize += values[i].size();
// EXPRESSION types don't contribute any placeholders.
if (ops[i] != UpdateOperation.EXPRESSION) {
setValuesSize += values[i].size();
}
}
int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
@ -277,6 +301,16 @@ public class DBUtils {
sql.append(colName);
}
break;
case EXPRESSION:
// Treat each value as a literal SQL string.
for (Map.Entry<String, Object> entry : v.valueSet()) {
final String colName = entry.getKey();
sql.append((arg > 0) ? "," : "");
sql.append(colName);
sql.append(" = ");
sql.append(entry.getValue());
}
break;
}
}

View File

@ -8,14 +8,19 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import org.mozilla.gecko.db.DBUtils.UpdateOperation;
import static org.mozilla.gecko.db.BrowserContract.ReadingListItems.*;
public class ReadingListProvider extends SharedBrowserDatabaseProvider {
private static final String LOGTAG = "GeckoRLProvider";
static final String TABLE_READING_LIST = TABLE_NAME;
static final int ITEMS = 101;
@ -68,7 +73,7 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
* This method does two things:
* * Based on the values provided, it computes and returns an incremental status change
* that can be applied to the database to track changes for syncing. This should be
* applied with {@link org.mozilla.gecko.db.DBUtils.UpdateOperation#BITWISE_OR}.
* applied with {@link UpdateOperation#BITWISE_OR}.
* * It mutates the provided values to mark absolute field changes.
*
* @return null if no values were provided, or no change needs to be recorded.
@ -78,9 +83,6 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
return null;
}
// Otherwise, it must have been modified.
values.put(SYNC_STATUS, SYNC_STATUS_MODIFIED);
final ContentValues out = new ContentValues();
int flag = 0;
if (values.containsKey(MARKED_READ_BY) ||
@ -120,13 +122,20 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
}
if (flags == null) {
// Dunno what we're doing with the DB that isn't changing anything we care about, but hey.
// This code path is used by Sync. Bypass metadata changes.
return db.update(TABLE_READING_LIST, values, selection, selectionArgs);
}
// Otherwise, we need to do smart updating to change flags.
final ContentValues[] valuesAndFlags = {values, flags};
final DBUtils.UpdateOperation[] ops = {DBUtils.UpdateOperation.ASSIGN, DBUtils.UpdateOperation.BITWISE_OR};
// Set synced items to MODIFIED; otherwise, leave the sync status untouched.
final ContentValues setModified = new ContentValues();
setModified.put(SYNC_STATUS, "CASE " + SYNC_STATUS +
" WHEN " + SYNC_STATUS_SYNCED +
" THEN " + SYNC_STATUS_MODIFIED +
" ELSE " + SYNC_STATUS +
" END");
final ContentValues[] valuesAndFlags = {values, flags, setModified};
final UpdateOperation[] ops = {UpdateOperation.ASSIGN, UpdateOperation.BITWISE_OR, UpdateOperation.EXPRESSION};
return DBUtils.updateArrays(db, TABLE_READING_LIST, valuesAndFlags, ops, selection, selectionArgs);
}
@ -157,7 +166,12 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
final String url = values.getAsString(URL);
debug("Inserting item in database with URL: " + url);
return getWritableDatabase(uri).insertOrThrow(TABLE_READING_LIST, null, values);
try {
return getWritableDatabase(uri).insertOrThrow(TABLE_READING_LIST, null, values);
} catch (SQLException e) {
Log.e(LOGTAG, "Insert failed.", e);
throw e;
}
}
private static final ContentValues DELETED_VALUES;
@ -318,6 +332,8 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
break;
default:
// Log here because we typically insert in a batch, and that will muffle.
Log.e(LOGTAG, "Unknown insert URI " + uri);
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
@ -327,6 +343,7 @@ public class ReadingListProvider extends SharedBrowserDatabaseProvider {
return ContentUris.withAppendedId(uri, id);
}
Log.e(LOGTAG, "Got to end of insertInTransaction without returning an id!");
return null;
}

View File

@ -198,6 +198,10 @@ public class ReadingListClientRecordFactory {
object.remove("client_last_modified");
object.remove("is_deleted");
// We never want to upload stored_on; for new items it'll be null (and cause Bug 1153358),
// and for existing items it should never change.
object.remove("stored_on");
object.remove(ReadingListItems.CONTENT_STATUS);
object.remove(ReadingListItems.SYNC_STATUS);
object.remove(ReadingListItems.SYNC_CHANGE_FLAGS);

View File

@ -287,7 +287,10 @@ let Reader = {
// Article hasn't been found in the cache, we need to
// download the page and parse the article out of it.
return yield ReaderMode.downloadAndParseDocument(url);
return yield ReaderMode.downloadAndParseDocument(url).catch(e => {
Cu.reportError("Error downloading and parsing document: " + e);
return null;
});;
}),
_getSavedArticle: function(browser) {

View File

@ -299,6 +299,7 @@ body {
list-style: none;
background-color: #EBEBF0;
border-top: 1px solid #D7D9DB;
display: none;
}
.toolbar[visible] {
@ -407,8 +408,8 @@ body {
flex: 0;
font-size: 24px;
color: #000000;
margin-left: 40px;
margin-right: 40px;
margin: 0 30px;
padding: 0 10px;
}
.serif-button {
@ -427,12 +428,14 @@ body {
.minus-button {
background-size: 24px 6px;
margin-left: 60px;
margin-left: 50px;
padding: 0 5px;
}
.plus-button {
background-size: 24px 24px;
margin-right: 60px;
margin-right: 50px;
padding: 0 5px;
}
#color-scheme-buttons > button {

View File

@ -4529,6 +4529,9 @@ pref("selectioncaret.enabled", false);
// user click on selection caret or not. In app units.
pref("selectioncaret.inflatesize.threshold", 40);
// Selection carets will fall-back to internal LongTap detector.
pref("selectioncaret.detects.longtap", true);
// Wakelock is disabled by default.
pref("dom.wakelock.enabled", false);

View File

@ -579,6 +579,11 @@ FxAccountsInternal.prototype = {
*/
version: DATA_FORMAT_VERSION,
// The timeout (in ms) we use to poll for a verified mail for the first 2 mins.
VERIFICATION_POLL_TIMEOUT_INITIAL: 5000, // 5 seconds
// And how often we poll after the first 2 mins.
VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 15000, // 15 seconds.
_fxAccountsClient: null,
get fxAccountsClient() {
@ -1128,7 +1133,8 @@ FxAccountsInternal.prototype = {
}
if (timeoutMs === undefined) {
let currentMinute = Math.ceil(ageMs / 60000);
timeoutMs = 1000 * (currentMinute <= 2 ? 5 : 15);
timeoutMs = currentMinute <= 2 ? this.VERIFICATION_POLL_TIMEOUT_INITIAL
: this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
}
log.debug("polling with timeout = " + timeoutMs);
this.currentTimer = setTimeout(() => {

View File

@ -25,7 +25,8 @@ let log = Log.repository.getLogger("Services.FxAccounts.test");
log.level = Log.Level.Debug;
// See verbose logging from FxAccounts.jsm
Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG");
Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
// The oauth server is mocked, but set these prefs to pass param checks
Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
@ -60,15 +61,10 @@ function MockFxAccountsClient() {
// user account has been verified
this.recoveryEmailStatus = function (sessionToken) {
// simulate a call to /recovery_email/status
let deferred = Promise.defer();
let response = {
return Promise.resolve({
email: this._email,
verified: this._verified
};
deferred.resolve(response);
return deferred.promise;
});
};
this.accountStatus = function(uid) {
@ -132,6 +128,8 @@ MockStorage.prototype = Object.freeze({
*/
function MockFxAccounts() {
return new FxAccounts({
VERIFICATION_POLL_TIMEOUT_INITIAL: 100, // 100ms
_getCertificateSigned_calls: [],
_d_signCertificate: Promise.defer(),
_now_is: new Date(),
@ -177,10 +175,12 @@ add_test(function test_non_https_remote_server_uri() {
});
add_task(function test_get_signed_in_user_initially_unset() {
// This test, unlike the rest, uses an un-mocked FxAccounts instance.
// However, we still need to pass an object to the constructor to
// force it to expose "internal", so we can test the disk storage.
let account = new FxAccounts({onlySetInternal: true})
// This test, unlike many of the the rest, uses a (largely) un-mocked
// FxAccounts instance.
// We do mock the storage to keep the test fast on b2g.
let account = new FxAccounts({
signedInUserStorage: new MockStorage(),
});
let credentials = {
email: "foo@example.com",
uid: "1234@lcip.org",
@ -190,6 +190,8 @@ add_task(function test_get_signed_in_user_initially_unset() {
kB: "cafe",
verified: true
};
// and a sad hack to ensure the mocked storage is used for the initial reads.
account.internal.currentAccountState.signedInUserStorage = account.internal.signedInUserStorage;
let result = yield account.getSignedInUser();
do_check_eq(result, null);
@ -218,12 +220,14 @@ add_task(function test_get_signed_in_user_initially_unset() {
do_check_eq(result, null);
});
add_task(function test_getCertificate() {
add_task(function* test_getCertificate() {
_("getCertificate()");
// This test, unlike the rest, uses an un-mocked FxAccounts instance.
// However, we still need to pass an object to the constructor to
// force it to expose "internal".
let fxa = new FxAccounts({onlySetInternal: true});
// This test, unlike many of the the rest, uses a (largely) un-mocked
// FxAccounts instance.
// We do mock the storage to keep the test fast on b2g.
let fxa = new FxAccounts({
signedInUserStorage: new MockStorage(),
});
let credentials = {
email: "foo@example.com",
uid: "1234@lcip.org",
@ -233,6 +237,8 @@ add_task(function test_getCertificate() {
kB: "cafe",
verified: true
};
// and a sad hack to ensure the mocked storage is used for the initial reads.
fxa.internal.currentAccountState.signedInUserStorage = fxa.internal.signedInUserStorage;
yield fxa.setSignedInUser(credentials);
// Test that an expired cert throws if we're offline.
@ -242,7 +248,7 @@ add_task(function test_getCertificate() {
let offline = Services.io.offline;
Services.io.offline = true;
// This call would break from missing parameters ...
fxa.internal.currentAccountState.getCertificate().then(
yield fxa.internal.currentAccountState.getCertificate().then(
result => {
Services.io.offline = offline;
do_throw("Unexpected success");
@ -253,7 +259,6 @@ add_task(function test_getCertificate() {
do_check_eq(err, "Error: OFFLINE");
}
);
_("----- DONE ----\n");
});

View File

@ -270,8 +270,12 @@ AboutReader.prototype = {
// Display the toolbar when all its initial component states are known
if (isInitialStateChange) {
// Hacks! Delay showing the toolbar to avoid position: fixed; jankiness. See bug 975533.
this._win.setTimeout(() => this._setToolbarVisibility(true), 500);
// Toolbar display is updated here to avoid it appearing in the middle of the screen on page load. See bug 1145567.
this._win.setTimeout(() => {
this._toolbarElement.style.display = "block";
// Delay showing the toolbar to have a nice slide from bottom animation.
this._win.setTimeout(() => this._setToolbarVisibility(true), 200);
}, 500);
}
}
}

View File

@ -131,6 +131,10 @@ this.ReaderMode = {
}
let doc = xhr.responseXML;
if (!doc) {
reject("Reader mode XHR didn't return a document");
return;
}
// Manually follow a meta refresh tag if one exists.
let meta = doc.querySelector("meta[http-equiv=refresh]");

View File

@ -13,6 +13,13 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var global = this;
// Lazily load the finder code
addMessageListener("Finder:Initialize", function () {
let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
new RemoteFinderListener(global);
});
let ClickEventHandler = {
init: function init() {
this._scrollable = null;

View File

@ -868,7 +868,11 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
nsWindow *target = win->FindWindowForPoint(pt);
if (target) {
// Send the contextmenu event to Gecko.
target->OnContextmenuEvent(ae);
if (!target->OnContextmenuEvent(ae)) {
// If not consumed, continue as a LongTap, possibly trigger
// Gecko Text Selection Carets.
target->OnLongTapEvent(ae);
}
}
break;
}
@ -1008,7 +1012,7 @@ nsWindow::OnMouseEvent(AndroidGeckoEvent *ae)
DispatchEvent(&event);
}
void
bool
nsWindow::OnContextmenuEvent(AndroidGeckoEvent *ae)
{
nsRefPtr<nsWindow> kungFuDeathGrip(this);
@ -1037,7 +1041,35 @@ nsWindow::OnContextmenuEvent(AndroidGeckoEvent *ae)
WidgetTouchEvent canceltouchEvent = ae->MakeTouchEvent(this);
canceltouchEvent.message = NS_TOUCH_CANCEL;
DispatchEvent(&canceltouchEvent);
return true;
}
return false;
}
void
nsWindow::OnLongTapEvent(AndroidGeckoEvent *ae)
{
nsRefPtr<nsWindow> kungFuDeathGrip(this);
CSSPoint pt;
const nsTArray<nsIntPoint>& points = ae->Points();
if (points.Length() > 0) {
pt = CSSPoint(points[0].x, points[0].y);
}
// Send the LongTap event to Gecko.
WidgetMouseEvent event(true, NS_MOUSE_MOZLONGTAP, this,
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
event.button = WidgetMouseEvent::eLeftButton;
event.refPoint =
RoundedToInt(pt * GetDefaultScale()) - WidgetToScreenOffset();
event.clickCount = 1;
event.time = ae->Time();
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
event.ignoreRootScrollFrame = true;
DispatchEvent(&event);
}
bool nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae)

View File

@ -49,7 +49,8 @@ public:
nsWindow* FindWindowForPoint(const nsIntPoint& pt);
void OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
bool OnContextmenuEvent(mozilla::AndroidGeckoEvent *ae);
void OnLongTapEvent(mozilla::AndroidGeckoEvent *ae);
bool OnMultitouchEvent(mozilla::AndroidGeckoEvent *ae);
void OnNativeGestureEvent(mozilla::AndroidGeckoEvent *ae);
void OnMouseEvent(mozilla::AndroidGeckoEvent *ae);