mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
8b35717b4c
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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
|
||||
|
596
browser/base/content/tab-content.js
Normal file
596
browser/base/content/tab-content.js
Normal 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();
|
@ -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)
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
115
browser/devtools/styleeditor/test/browser_styleeditor_opentab.js
Normal file
115
browser/devtools/styleeditor/test/browser_styleeditor_opentab.js
Normal 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;
|
||||
}
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
});
|
||||
})
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -271,6 +271,7 @@ private:
|
||||
|
||||
// Preference
|
||||
static int32_t sSelectionCaretsInflateSize;
|
||||
static bool sSelectionCaretDetectsLongTap;
|
||||
};
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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(() => {
|
||||
|
@ -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");
|
||||
});
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]");
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user