Merge fx-team to m-c. a=merge
|
@ -9,6 +9,21 @@ var FullScreen = {
|
|||
delete this._fullScrToggler;
|
||||
return this._fullScrToggler = document.getElementById("fullscr-toggler");
|
||||
},
|
||||
|
||||
init: function() {
|
||||
// called when we go into full screen, even if initiated by a web page script
|
||||
window.addEventListener("fullscreen", this, true);
|
||||
window.messageManager.addMessageListener("MozEnteredDomFullscreen", this);
|
||||
|
||||
if (window.fullScreen)
|
||||
this.toggle();
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
window.messageManager.removeMessageListener("MozEnteredDomFullscreen", this);
|
||||
this.cleanup();
|
||||
},
|
||||
|
||||
toggle: function (event) {
|
||||
var enterFS = window.fullScreen;
|
||||
|
||||
|
@ -95,9 +110,12 @@ var FullScreen = {
|
|||
switch (event.type) {
|
||||
case "activate":
|
||||
if (document.mozFullScreen) {
|
||||
this.showWarning(this.fullscreenDoc);
|
||||
this.showWarning(this.fullscreenOrigin);
|
||||
}
|
||||
break;
|
||||
case "fullscreen":
|
||||
this.toggle(event);
|
||||
break;
|
||||
case "transitionend":
|
||||
if (event.propertyName == "opacity")
|
||||
this.cancelWarning();
|
||||
|
@ -105,18 +123,33 @@ var FullScreen = {
|
|||
}
|
||||
},
|
||||
|
||||
enterDomFullscreen : function(event) {
|
||||
receiveMessage: function(aMessage) {
|
||||
if (aMessage.name == "MozEnteredDomFullscreen") {
|
||||
// If we're a multiprocess browser, then the request to enter fullscreen
|
||||
// did not bubble up to the root browser document - it stopped at the root
|
||||
// of the content document. That means we have to kick off the switch to
|
||||
// fullscreen here at the operating system level in the parent process
|
||||
// ourselves.
|
||||
let data = aMessage.data;
|
||||
let browser = aMessage.target;
|
||||
if (gMultiProcessBrowser && browser.getAttribute("remote") == "true") {
|
||||
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.remoteFrameFullscreenChanged(browser, data.origin);
|
||||
}
|
||||
this.enterDomFullscreen(browser, data.origin);
|
||||
}
|
||||
},
|
||||
|
||||
enterDomFullscreen : function(aBrowser, aOrigin) {
|
||||
if (!document.mozFullScreen)
|
||||
return;
|
||||
|
||||
// However, if we receive a "MozEnteredDomFullScreen" event for a document
|
||||
// which is not a subdocument of a currently active (ie. visible) browser
|
||||
// or iframe, we know that we've switched to a different frame since the
|
||||
// request to enter full-screen was made, so we should exit full-screen
|
||||
// since the "full-screen document" isn't acutally visible.
|
||||
if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).isActive) {
|
||||
// If we've received a fullscreen notification, we have to ensure that the
|
||||
// element that's requesting fullscreen belongs to the browser that's currently
|
||||
// active. If not, we exit fullscreen since the "full-screen document" isn't
|
||||
// actually visible now.
|
||||
if (gBrowser.selectedBrowser != aBrowser) {
|
||||
document.mozCancelFullScreen();
|
||||
return;
|
||||
}
|
||||
|
@ -136,7 +169,7 @@ var FullScreen = {
|
|||
if (gFindBarInitialized)
|
||||
gFindBar.close();
|
||||
|
||||
this.showWarning(event.target);
|
||||
this.showWarning(aOrigin);
|
||||
|
||||
// Exit DOM full-screen mode upon open, close, or change tab.
|
||||
gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
|
||||
|
@ -178,7 +211,9 @@ var FullScreen = {
|
|||
gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
|
||||
if (!this.useLionFullScreen)
|
||||
window.removeEventListener("activate", this);
|
||||
this.fullscreenDoc = null;
|
||||
|
||||
window.messageManager
|
||||
.broadcastAsyncMessage("DOMFullscreen:Cleanup");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -337,7 +372,7 @@ var FullScreen = {
|
|||
// the permission manager can't handle (documents with URIs without a host).
|
||||
// We simply require those to be approved every time instead.
|
||||
let rememberCheckbox = document.getElementById("full-screen-remember-decision");
|
||||
let uri = this.fullscreenDoc.nodePrincipal.URI;
|
||||
let uri = BrowserUtils.makeURI(this.fullscreenOrigin);
|
||||
if (!rememberCheckbox.hidden) {
|
||||
if (rememberCheckbox.checked)
|
||||
Services.perms.add(uri,
|
||||
|
@ -370,27 +405,29 @@ var FullScreen = {
|
|||
// If the document has been granted fullscreen, notify Gecko so it can resume
|
||||
// any pending pointer lock requests, otherwise exit fullscreen; the user denied
|
||||
// the fullscreen request.
|
||||
if (isApproved)
|
||||
Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
|
||||
else
|
||||
if (isApproved) {
|
||||
gBrowser.selectedBrowser
|
||||
.messageManager
|
||||
.sendAsyncMessage("DOMFullscreen:Approved");
|
||||
} else {
|
||||
document.mozCancelFullScreen();
|
||||
}
|
||||
},
|
||||
|
||||
warningBox: null,
|
||||
warningFadeOutTimeout: null,
|
||||
fullscreenDoc: null,
|
||||
|
||||
// Shows the fullscreen approval UI, or if the domain has already been approved
|
||||
// for fullscreen, shows a warning that the site has entered fullscreen for a short
|
||||
// duration.
|
||||
showWarning: function(targetDoc) {
|
||||
showWarning: function(aOrigin) {
|
||||
if (!document.mozFullScreen ||
|
||||
!gPrefService.getBoolPref("full-screen-api.approval-required"))
|
||||
return;
|
||||
|
||||
// Set the strings on the fullscreen approval UI.
|
||||
this.fullscreenDoc = targetDoc;
|
||||
let uri = this.fullscreenDoc.nodePrincipal.URI;
|
||||
this.fullscreenOrigin = aOrigin;
|
||||
let uri = BrowserUtils.makeURI(aOrigin);
|
||||
let host = null;
|
||||
try {
|
||||
host = uri.host;
|
||||
|
|
|
@ -23,6 +23,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
|
|||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
|
||||
"resource://gre/modules/GMPInstallManager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
||||
"resource://gre/modules/NewTabUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
|
||||
"resource:///modules/ContentSearch.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
|
||||
|
@ -1294,17 +1296,7 @@ var gBrowserInit = {
|
|||
if (Win7Features)
|
||||
Win7Features.onOpenWindow();
|
||||
|
||||
// called when we go into full screen, even if initiated by a web page script
|
||||
window.addEventListener("fullscreen", onFullScreen, true);
|
||||
|
||||
// Called when we enter DOM full-screen mode. Note we can already be in browser
|
||||
// full-screen mode when we enter DOM full-screen mode.
|
||||
window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
|
||||
|
||||
if (window.fullScreen)
|
||||
onFullScreen();
|
||||
if (document.mozFullScreen)
|
||||
onMozEnteredDomFullscreen();
|
||||
FullScreen.init();
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
// initialize the sync UI
|
||||
|
@ -1435,7 +1427,7 @@ var gBrowserInit = {
|
|||
|
||||
gHistorySwipeAnimation.uninit();
|
||||
|
||||
FullScreen.cleanup();
|
||||
FullScreen.uninit();
|
||||
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
gFxAccounts.uninit();
|
||||
|
@ -2762,14 +2754,6 @@ function SwitchToMetro() {
|
|||
#endif
|
||||
}
|
||||
|
||||
function onFullScreen(event) {
|
||||
FullScreen.toggle(event);
|
||||
}
|
||||
|
||||
function onMozEnteredDomFullscreen(event) {
|
||||
FullScreen.enterDomFullscreen(event);
|
||||
}
|
||||
|
||||
function getWebNavigation()
|
||||
{
|
||||
return gBrowser.webNavigation;
|
||||
|
@ -3109,7 +3093,7 @@ const BrowserSearch = {
|
|||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
if (url === "about:home") {
|
||||
AboutHome.focusInput(mm);
|
||||
} else if (url === "about:newtab") {
|
||||
} else if (url === "about:newtab" && NewTabUtils.allPages.enabled) {
|
||||
ContentSearch.focusInput(mm);
|
||||
} else {
|
||||
openUILinkIn("about:home", "current");
|
||||
|
|
|
@ -578,3 +578,40 @@ 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();
|
|
@ -1225,6 +1225,15 @@
|
|||
// If we're using remote tabs, we have to wait until after we've finalized
|
||||
// switching the tabs.
|
||||
|
||||
if (newTab._skipContentFocus) {
|
||||
// It's possible the tab we're switching to is ready to focus asynchronously,
|
||||
// when we've already focused something else. In that case, this
|
||||
// _skipContentFocus property can be set so that we skip focusing the
|
||||
// content after we switch tabs.
|
||||
delete newTab._skipContentFocus;
|
||||
return;
|
||||
}
|
||||
|
||||
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
|
||||
let focusFlags = fm.FLAG_NOSCROLL;
|
||||
|
||||
|
|
|
@ -205,6 +205,24 @@ function runTests() {
|
|||
EventUtils.synthesizeKey("k", { accelKey: true });
|
||||
is(searchBar.textbox.inputField, gWindow.document.activeElement, "Toolbar's search bar should be focused");
|
||||
|
||||
// Test that Ctrl/Cmd + K will focus the search bar from a new about:home page if
|
||||
// the newtab is disabled from `NewTabUtils.allPages.enabled`.
|
||||
yield addNewTabPageTab();
|
||||
// Remove the search bar from toolbar
|
||||
CustomizableUI.removeWidgetFromArea("search-container");
|
||||
NewTabUtils.allPages.enabled = false;
|
||||
EventUtils.synthesizeKey("k", { accelKey: true });
|
||||
let waitEvent = "AboutHomeLoadSnippetsCompleted";
|
||||
yield promiseTabLoadEvent(gWindow.gBrowser.selectedTab, "about:home", waitEvent).then(TestRunner.next);
|
||||
|
||||
is(getContentDocument().documentURI.toLowerCase(), "about:home", "New tab's uri should be about:home");
|
||||
let searchInput = getContentDocument().getElementById("searchText");
|
||||
is(searchInput, getContentDocument().activeElement, "Search input must be the selected element");
|
||||
|
||||
NewTabUtils.allPages.enabled = true;
|
||||
CustomizableUI.reset();
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
// Done. Revert the current engine and remove the new engines.
|
||||
Services.search.currentEngine = oldCurrentEngine;
|
||||
yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
|
||||
|
@ -414,3 +432,46 @@ function logoImg() {
|
|||
function gSearch() {
|
||||
return getContentWindow().gSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a load (or custom) event to finish in a given tab. If provided
|
||||
* load an uri into the tab.
|
||||
*
|
||||
* @param tab
|
||||
* The tab to load into.
|
||||
* @param [optional] url
|
||||
* The url to load, or the current url.
|
||||
* @param [optional] event
|
||||
* The load event type to wait for. Defaults to "load".
|
||||
* @return {Promise} resolved when the event is handled.
|
||||
* @resolves to the received event
|
||||
* @rejects if a valid load event is not received within a meaningful interval
|
||||
*/
|
||||
function promiseTabLoadEvent(tab, url, eventType="load") {
|
||||
let deferred = Promise.defer();
|
||||
info("Wait tab event: " + eventType);
|
||||
|
||||
function handle(event) {
|
||||
if (event.originalTarget != tab.linkedBrowser.contentDocument ||
|
||||
event.target.location.href == "about:blank" ||
|
||||
(url && event.target.location.href != url)) {
|
||||
info("Skipping spurious '" + eventType + "'' event" +
|
||||
" for " + event.target.location.href);
|
||||
return;
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
tab.linkedBrowser.removeEventListener(eventType, handle, true);
|
||||
info("Tab event received: " + eventType);
|
||||
deferred.resolve(event);
|
||||
}
|
||||
|
||||
let timeout = setTimeout(() => {
|
||||
tab.linkedBrowser.removeEventListener(eventType, handle, true);
|
||||
deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
|
||||
}, 20000);
|
||||
|
||||
tab.linkedBrowser.addEventListener(eventType, handle, true, true);
|
||||
if (url)
|
||||
tab.linkedBrowser.loadURI(url);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
|||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("chrome://browser/content/sanitize.js", tmp);
|
||||
Cu.import("resource://gre/modules/Timer.jsm", tmp);
|
||||
let {Promise, NewTabUtils, Sanitizer, clearTimeout, DirectoryLinksProvider} = tmp;
|
||||
let {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider} = tmp;
|
||||
|
||||
let uri = Services.io.newURI("about:newtab", null, null);
|
||||
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
|
||||
|
|
|
@ -309,6 +309,8 @@ function openLinkIn(url, where, params) {
|
|||
// result in a new frontmost window (e.g. "javascript:window.open('');").
|
||||
w.focus();
|
||||
|
||||
let newTab;
|
||||
|
||||
switch (where) {
|
||||
case "current":
|
||||
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
||||
|
@ -331,23 +333,30 @@ function openLinkIn(url, where, params) {
|
|||
loadInBackground = !loadInBackground;
|
||||
// fall through
|
||||
case "tab":
|
||||
let browser = w.gBrowser;
|
||||
browser.loadOneTab(url, {
|
||||
referrerURI: aReferrerURI,
|
||||
charset: aCharset,
|
||||
postData: aPostData,
|
||||
inBackground: loadInBackground,
|
||||
allowThirdPartyFixup: aAllowThirdPartyFixup,
|
||||
relatedToCurrent: aRelatedToCurrent,
|
||||
skipAnimation: aSkipTabAnimation,
|
||||
allowMixedContent: aAllowMixedContent });
|
||||
newTab = w.gBrowser.loadOneTab(url, {
|
||||
referrerURI: aReferrerURI,
|
||||
charset: aCharset,
|
||||
postData: aPostData,
|
||||
inBackground: loadInBackground,
|
||||
allowThirdPartyFixup: aAllowThirdPartyFixup,
|
||||
relatedToCurrent: aRelatedToCurrent,
|
||||
skipAnimation: aSkipTabAnimation,
|
||||
allowMixedContent: aAllowMixedContent
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
w.gBrowser.selectedBrowser.focus();
|
||||
|
||||
if (!loadInBackground && w.isBlankPageURL(url))
|
||||
if (!loadInBackground && w.isBlankPageURL(url)) {
|
||||
if (newTab && gMultiProcessBrowser) {
|
||||
// Remote browsers are switched to asynchronously, and we need to
|
||||
// ensure that the location bar remains focused in that case rather
|
||||
// than the content area being focused.
|
||||
newTab._skipContentFocus = true;
|
||||
}
|
||||
w.focusAndSelectUrlBar();
|
||||
}
|
||||
}
|
||||
|
||||
// Used as an onclick handler for UI elements with link-like behavior.
|
||||
|
|
|
@ -7,24 +7,75 @@ this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/Chr
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
|
||||
"resource://gre/modules/FormHistory.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
function expectedURL(aSearchTerms) {
|
||||
const ENGINE_HTML_BASE = "http://mochi.test:8888/browser/browser/components/search/test/test.html";
|
||||
var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
|
||||
getService(Ci.nsITextToSubURI);
|
||||
var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
|
||||
return ENGINE_HTML_BASE + "?test=" + searchArg;
|
||||
}
|
||||
|
||||
var searchEntries = ["test", "More Text", "Some Text"];
|
||||
var searchBar = BrowserSearch.searchBar;
|
||||
var searchButton = document.getAnonymousElementByAttribute(searchBar,
|
||||
"anonid", "search-go-button");
|
||||
ok(searchButton, "got search-go-button");
|
||||
function simulateClick(aEvent, aTarget) {
|
||||
var event = document.createEvent("MouseEvent");
|
||||
var ctrlKeyArg = aEvent.ctrlKey || false;
|
||||
var altKeyArg = aEvent.altKey || false;
|
||||
var shiftKeyArg = aEvent.shiftKey || false;
|
||||
var metaKeyArg = aEvent.metaKey || false;
|
||||
var buttonArg = aEvent.button || 0;
|
||||
event.initMouseEvent("click", true, true, window,
|
||||
0, 0, 0, 0, 0,
|
||||
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
|
||||
buttonArg, null);
|
||||
aTarget.dispatchEvent(event);
|
||||
}
|
||||
|
||||
searchBar.value = "test";
|
||||
// modified from toolkit/components/satchel/test/test_form_autocomplete.html
|
||||
function checkMenuEntries(expectedValues) {
|
||||
var actualValues = getMenuEntries();
|
||||
is(actualValues.length, expectedValues.length, "Checking length of expected menu");
|
||||
for (var i = 0; i < expectedValues.length; i++)
|
||||
is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
|
||||
}
|
||||
|
||||
function getMenuEntries() {
|
||||
var entries = [];
|
||||
var autocompleteMenu = searchBar.textbox.popup;
|
||||
// Could perhaps pull values directly from the controller, but it seems
|
||||
// more reliable to test the values that are actually in the tree?
|
||||
var column = autocompleteMenu.tree.columns[0];
|
||||
var numRows = autocompleteMenu.tree.view.rowCount;
|
||||
for (var i = 0; i < numRows; i++) {
|
||||
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function* countEntries(name, value) {
|
||||
let deferred = Promise.defer();
|
||||
let count = 0;
|
||||
let obj = name && value ? {fieldname: name, value: value} : {};
|
||||
FormHistory.count(obj,
|
||||
{ handleResult: function(result) { count = result; },
|
||||
handleError: function(error) { throw error; },
|
||||
handleCompletion: function(reason) {
|
||||
if (!reason) {
|
||||
deferred.resolve(count);
|
||||
}
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
var searchBar;
|
||||
var searchButton;
|
||||
var searchEntries = ["test", "More Text", "Some Text"];
|
||||
function* promiseSetEngine() {
|
||||
let deferred = Promise.defer();
|
||||
var ss = Services.search;
|
||||
|
||||
let testIterator;
|
||||
|
||||
function observer(aSub, aTopic, aData) {
|
||||
switch (aData) {
|
||||
case "engine-added":
|
||||
|
@ -34,266 +85,221 @@ function test() {
|
|||
break;
|
||||
case "engine-current":
|
||||
ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
|
||||
testReturn();
|
||||
break;
|
||||
case "engine-removed":
|
||||
searchBar = BrowserSearch.searchBar;
|
||||
searchButton = document.getAnonymousElementByAttribute(searchBar,
|
||||
"anonid", "search-go-button");
|
||||
ok(searchButton, "got search-go-button");
|
||||
searchBar.value = "test";
|
||||
|
||||
Services.obs.removeObserver(observer, "browser-search-engine-modified");
|
||||
finish();
|
||||
deferred.resolve();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "browser-search-engine-modified", false);
|
||||
ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
|
||||
Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
|
||||
false);
|
||||
|
||||
var preSelectedBrowser, preTabNo;
|
||||
function init() {
|
||||
preSelectedBrowser = gBrowser.selectedBrowser;
|
||||
preTabNo = gBrowser.tabs.length;
|
||||
searchBar.focus();
|
||||
}
|
||||
|
||||
function testReturn() {
|
||||
init();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
doOnloadOnce(function(event) {
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
|
||||
is(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Return key loaded results in current tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testReturn opened correct search page");
|
||||
|
||||
testAltReturn();
|
||||
});
|
||||
}
|
||||
|
||||
function testAltReturn() {
|
||||
init();
|
||||
EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
|
||||
doOnloadOnce(function(event) {
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Alt+Return key loaded results in new tab");
|
||||
is(event.originalTarget, gBrowser.contentDocument,
|
||||
"Alt+Return key loaded results in foreground tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testAltReturn opened correct search page");
|
||||
|
||||
//Shift key has no effect for now, so skip it
|
||||
//testShiftAltReturn();
|
||||
testLeftClick();
|
||||
});
|
||||
}
|
||||
|
||||
function testShiftAltReturn() {
|
||||
init();
|
||||
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
|
||||
doOnloadOnce(function(event) {
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Shift+Alt+Return key loaded results in new tab");
|
||||
isnot(event.originalTarget, gBrowser.contentDocument,
|
||||
"Shift+Alt+Return key loaded results in background tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftAltReturn opened correct search page");
|
||||
|
||||
testLeftClick();
|
||||
});
|
||||
}
|
||||
|
||||
function testLeftClick() {
|
||||
init();
|
||||
simulateClick({ button: 0 }, searchButton);
|
||||
doOnloadOnce(function(event) {
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
|
||||
is(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"LeftClick loaded results in current tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testLeftClick opened correct search page");
|
||||
|
||||
testMiddleClick();
|
||||
});
|
||||
}
|
||||
|
||||
function testMiddleClick() {
|
||||
init();
|
||||
simulateClick({ button: 1 }, searchButton);
|
||||
doOnloadOnce(function(event) {
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"MiddleClick loaded results in new tab");
|
||||
is(event.originalTarget, gBrowser.contentDocument,
|
||||
"MiddleClick loaded results in foreground tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
|
||||
|
||||
testShiftMiddleClick();
|
||||
});
|
||||
}
|
||||
|
||||
function testShiftMiddleClick() {
|
||||
init();
|
||||
simulateClick({ button: 1, shiftKey: true }, searchButton);
|
||||
doOnloadOnce(function(event) {
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Shift+MiddleClick loaded results in new tab");
|
||||
isnot(event.originalTarget, gBrowser.contentDocument,
|
||||
"Shift+MiddleClick loaded results in background tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftMiddleClick opened correct search page");
|
||||
|
||||
testDropText();
|
||||
});
|
||||
}
|
||||
|
||||
// prevent the search buttonmenu from opening during the drag tests
|
||||
function stopPopup(event) { event.preventDefault(); }
|
||||
|
||||
function testDropText() {
|
||||
init();
|
||||
searchBar.addEventListener("popupshowing", stopPopup, true);
|
||||
// drop on the search button so that we don't need to worry about the
|
||||
// default handlers for textboxes.
|
||||
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/plain", data: "Some Text" } ]], "copy", window);
|
||||
doOnloadOnce(function(event) {
|
||||
is(searchBar.value, "Some Text", "drop text/plain on searchbar");
|
||||
testDropInternalText();
|
||||
});
|
||||
}
|
||||
|
||||
function testDropInternalText() {
|
||||
init();
|
||||
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/x-moz-text-internal", data: "More Text" } ]], "copy", window);
|
||||
doOnloadOnce(function(event) {
|
||||
is(searchBar.value, "More Text", "drop text/x-moz-text-internal on searchbar");
|
||||
testDropLink();
|
||||
});
|
||||
}
|
||||
|
||||
function testDropLink() {
|
||||
init();
|
||||
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/uri-list", data: "http://www.mozilla.org" } ]], "copy", window);
|
||||
is(searchBar.value, "More Text", "drop text/uri-list on searchbar");
|
||||
SimpleTest.executeSoon(testRightClick);
|
||||
}
|
||||
|
||||
function testRightClick() {
|
||||
init();
|
||||
searchBar.removeEventListener("popupshowing", stopPopup, true);
|
||||
content.location.href = "about:blank";
|
||||
simulateClick({ button: 2 }, searchButton);
|
||||
setTimeout(function() {
|
||||
is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
|
||||
is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
|
||||
|
||||
testIterator = testSearchHistory();
|
||||
testIterator.next();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function countEntries(name, value, message) {
|
||||
let count = 0;
|
||||
FormHistory.count({ fieldname: name, value: value },
|
||||
{ handleResult: function(result) { count = result; },
|
||||
handleError: function(error) { throw error; },
|
||||
handleCompletion: function(reason) {
|
||||
if (!reason) {
|
||||
ok(count > 0, message);
|
||||
testIterator.next();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function testSearchHistory() {
|
||||
var textbox = searchBar._textbox;
|
||||
for (var i = 0; i < searchEntries.length; i++) {
|
||||
yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i],
|
||||
"form history entry '" + searchEntries[i] + "' should exist");
|
||||
}
|
||||
testAutocomplete();
|
||||
}
|
||||
|
||||
function testAutocomplete() {
|
||||
var popup = searchBar.textbox.popup;
|
||||
popup.addEventListener("popupshown", function testACPopupShowing() {
|
||||
popup.removeEventListener("popupshown", testACPopupShowing);
|
||||
checkMenuEntries(searchEntries);
|
||||
testClearHistory();
|
||||
});
|
||||
searchBar.textbox.showHistoryPopup();
|
||||
}
|
||||
|
||||
function testClearHistory() {
|
||||
let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
|
||||
ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
|
||||
controller.doCommand("cmd_clearhistory");
|
||||
let count = 0;
|
||||
FormHistory.count({ },
|
||||
{ handleResult: function(result) { count = result; },
|
||||
handleError: function(error) { throw error; },
|
||||
handleCompletion: function(reason) {
|
||||
if (!reason) {
|
||||
ok(count == 0, "History cleared");
|
||||
finalize();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function finalize() {
|
||||
searchBar.value = "";
|
||||
while (gBrowser.tabs.length != 1) {
|
||||
gBrowser.removeTab(gBrowser.tabs[0]);
|
||||
}
|
||||
content.location.href = "about:blank";
|
||||
var engine = ss.getEngineByName("Bug 426329");
|
||||
ss.removeEngine(engine);
|
||||
}
|
||||
|
||||
function simulateClick(aEvent, aTarget) {
|
||||
var event = document.createEvent("MouseEvent");
|
||||
var ctrlKeyArg = aEvent.ctrlKey || false;
|
||||
var altKeyArg = aEvent.altKey || false;
|
||||
var shiftKeyArg = aEvent.shiftKey || false;
|
||||
var metaKeyArg = aEvent.metaKey || false;
|
||||
var buttonArg = aEvent.button || 0;
|
||||
event.initMouseEvent("click", true, true, window,
|
||||
0, 0, 0, 0, 0,
|
||||
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
|
||||
buttonArg, null);
|
||||
aTarget.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function expectedURL(aSearchTerms) {
|
||||
var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
|
||||
getService(Ci.nsITextToSubURI);
|
||||
var searchArg = textToSubURI.ConvertAndEscape("utf-8", aSearchTerms);
|
||||
return ENGINE_HTML_BASE + "?test=" + searchArg;
|
||||
}
|
||||
|
||||
// modified from toolkit/components/satchel/test/test_form_autocomplete.html
|
||||
function checkMenuEntries(expectedValues) {
|
||||
var actualValues = getMenuEntries();
|
||||
is(actualValues.length, expectedValues.length, "Checking length of expected menu");
|
||||
for (var i = 0; i < expectedValues.length; i++)
|
||||
is(actualValues[i], expectedValues[i], "Checking menu entry #" + i);
|
||||
}
|
||||
|
||||
function getMenuEntries() {
|
||||
var entries = [];
|
||||
var autocompleteMenu = searchBar.textbox.popup;
|
||||
// Could perhaps pull values directly from the controller, but it seems
|
||||
// more reliable to test the values that are actually in the tree?
|
||||
var column = autocompleteMenu.tree.columns[0];
|
||||
var numRows = autocompleteMenu.tree.view.rowCount;
|
||||
for (var i = 0; i < numRows; i++) {
|
||||
entries.push(autocompleteMenu.tree.view.getValueAt(i, column));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function* promiseRemoveEngine() {
|
||||
let deferred = Promise.defer();
|
||||
var ss = Services.search;
|
||||
|
||||
function observer(aSub, aTopic, aData) {
|
||||
if (aData == "engine-removed") {
|
||||
Services.obs.removeObserver(observer, "browser-search-engine-modified");
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
Services.obs.addObserver(observer, "browser-search-engine-modified", false);
|
||||
var engine = ss.getEngineByName("Bug 426329");
|
||||
ss.removeEngine(engine);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
var preSelectedBrowser;
|
||||
var preTabNo;
|
||||
function* prepareTest() {
|
||||
preSelectedBrowser = gBrowser.selectedBrowser;
|
||||
preTabNo = gBrowser.tabs.length;
|
||||
searchBar = BrowserSearch.searchBar;
|
||||
|
||||
let windowFocused = Promise.defer();
|
||||
SimpleTest.waitForFocus(windowFocused.resolve, window);
|
||||
yield windowFocused.promise;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
if (document.activeElement != searchBar) {
|
||||
searchBar.addEventListener("focus", function onFocus() {
|
||||
searchBar.removeEventListener("focus", onFocus);
|
||||
deferred.resolve();
|
||||
});
|
||||
searchBar.focus();
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
add_task(function testSetupEngine() {
|
||||
yield promiseSetEngine();
|
||||
});
|
||||
|
||||
add_task(function testReturn() {
|
||||
yield prepareTest();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
let event = yield promiseOnLoad();
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo, "Return key did not open new tab");
|
||||
is(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Return key loaded results in current tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testReturn opened correct search page");
|
||||
});
|
||||
|
||||
add_task(function testAltReturn() {
|
||||
yield prepareTest();
|
||||
EventUtils.synthesizeKey("VK_RETURN", { altKey: true });
|
||||
let event = yield promiseOnLoad();
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Alt+Return key added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Alt+Return key loaded results in new tab");
|
||||
is(event.originalTarget, gBrowser.contentDocument,
|
||||
"Alt+Return key loaded results in foreground tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testAltReturn opened correct search page");
|
||||
});
|
||||
|
||||
//Shift key has no effect for now, so skip it
|
||||
add_task(function testShiftAltReturn() {
|
||||
return;
|
||||
|
||||
yield prepareTest();
|
||||
EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true, altKey: true });
|
||||
let event = yield promiseOnLoad();
|
||||
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Shift+Alt+Return key added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Shift+Alt+Return key loaded results in new tab");
|
||||
isnot(event.originalTarget, gBrowser.contentDocument,
|
||||
"Shift+Alt+Return key loaded results in background tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftAltReturn opened correct search page");
|
||||
});
|
||||
|
||||
add_task(function testLeftClick() {
|
||||
yield prepareTest();
|
||||
simulateClick({ button: 0 }, searchButton);
|
||||
let event = yield promiseOnLoad();
|
||||
is(gBrowser.tabs.length, preTabNo, "LeftClick did not open new tab");
|
||||
is(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"LeftClick loaded results in current tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testLeftClick opened correct search page");
|
||||
});
|
||||
|
||||
add_task(function testMiddleClick() {
|
||||
yield prepareTest();
|
||||
simulateClick({ button: 1 }, searchButton);
|
||||
let event = yield promiseOnLoad();
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "MiddleClick added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"MiddleClick loaded results in new tab");
|
||||
is(event.originalTarget, gBrowser.contentDocument,
|
||||
"MiddleClick loaded results in foreground tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testMiddleClick opened correct search page");
|
||||
});
|
||||
|
||||
add_task(function testShiftMiddleClick() {
|
||||
yield prepareTest();
|
||||
simulateClick({ button: 1, shiftKey: true }, searchButton);
|
||||
let event = yield promiseOnLoad();
|
||||
is(gBrowser.tabs.length, preTabNo + 1, "Shift+MiddleClick added new tab");
|
||||
isnot(event.originalTarget, preSelectedBrowser.contentDocument,
|
||||
"Shift+MiddleClick loaded results in new tab");
|
||||
isnot(event.originalTarget, gBrowser.contentDocument,
|
||||
"Shift+MiddleClick loaded results in background tab");
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testShiftMiddleClick opened correct search page");
|
||||
});
|
||||
|
||||
add_task(function testDropText() {
|
||||
yield prepareTest();
|
||||
let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
|
||||
// drop on the search button so that we don't need to worry about the
|
||||
// default handlers for textboxes.
|
||||
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/plain", data: "Some Text" } ]], "copy", window);
|
||||
yield promisePreventPopup;
|
||||
let event = yield promiseOnLoad();
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testDropText opened correct search page");
|
||||
is(searchBar.value, "Some Text", "drop text/plain on searchbar");
|
||||
});
|
||||
|
||||
add_task(function testDropInternalText() {
|
||||
yield prepareTest();
|
||||
let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
|
||||
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/x-moz-text-internal", data: "More Text" } ]], "copy", window);
|
||||
yield promisePreventPopup;
|
||||
let event = yield promiseOnLoad();
|
||||
is(event.originalTarget.URL, expectedURL(searchBar.value), "testDropInternalText opened correct search page");
|
||||
is(searchBar.value, "More Text", "drop text/x-moz-text-internal on searchbar");
|
||||
|
||||
// testDropLink implicitly depended on testDropInternalText, so these two tests
|
||||
// were merged so that if testDropInternalText failed it wouldn't cause testDropLink
|
||||
// to fail unexplainably.
|
||||
yield prepareTest();
|
||||
let promisePreventPopup = promiseEvent(searchBar, "popupshowing", true);
|
||||
ChromeUtils.synthesizeDrop(searchBar.searchButton, searchBar.searchButton, [[ {type: "text/uri-list", data: "http://www.mozilla.org" } ]], "copy", window);
|
||||
yield promisePreventPopup;
|
||||
is(searchBar.value, "More Text", "drop text/uri-list on searchbar shouldn't change anything");
|
||||
});
|
||||
|
||||
add_task(function testRightClick() {
|
||||
preTabNo = gBrowser.tabs.length;
|
||||
content.location.href = "about:blank";
|
||||
simulateClick({ button: 2 }, searchButton);
|
||||
let deferred = Promise.defer();
|
||||
setTimeout(function() {
|
||||
is(gBrowser.tabs.length, preTabNo, "RightClick did not open new tab");
|
||||
is(gBrowser.currentURI.spec, "about:blank", "RightClick did nothing");
|
||||
deferred.resolve();
|
||||
}, 5000);
|
||||
yield deferred.promise;
|
||||
});
|
||||
|
||||
add_task(function testSearchHistory() {
|
||||
var textbox = searchBar._textbox;
|
||||
for (var i = 0; i < searchEntries.length; i++) {
|
||||
let count = yield countEntries(textbox.getAttribute("autocompletesearchparam"), searchEntries[i]);
|
||||
ok(count > 0, "form history entry '" + searchEntries[i] + "' should exist");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function testAutocomplete() {
|
||||
var popup = searchBar.textbox.popup;
|
||||
let popupShownPromise = promiseEvent(popup, "popupshown");
|
||||
searchBar.textbox.showHistoryPopup();
|
||||
yield popupShownPromise;
|
||||
checkMenuEntries(searchEntries);
|
||||
});
|
||||
|
||||
add_task(function testClearHistory() {
|
||||
let controller = searchBar.textbox.controllers.getControllerForCommand("cmd_clearhistory")
|
||||
ok(controller.isCommandEnabled("cmd_clearhistory"), "Clear history command enabled");
|
||||
controller.doCommand("cmd_clearhistory");
|
||||
let count = yield countEntries();
|
||||
ok(count == 0, "History cleared");
|
||||
});
|
||||
|
||||
add_task(function asyncCleanup() {
|
||||
searchBar.value = "";
|
||||
while (gBrowser.tabs.length != 1) {
|
||||
gBrowser.removeTab(gBrowser.tabs[0], {animate: false});
|
||||
}
|
||||
content.location.href = "about:blank";
|
||||
yield promiseRemoveEngine();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
|
||||
function whenNewWindowLoaded(aOptions, aCallback) {
|
||||
let win = OpenBrowserWindow(aOptions);
|
||||
let gotLoad = false;
|
||||
|
@ -88,6 +91,18 @@ function waitForPopupShown(aPopupId, aCallback) {
|
|||
registerCleanupFunction(removePopupShownListener);
|
||||
}
|
||||
|
||||
function* promiseEvent(aTarget, aEventName, aPreventDefault) {
|
||||
let deferred = Promise.defer();
|
||||
aTarget.addEventListener(aEventName, function onEvent(aEvent) {
|
||||
aTarget.removeEventListener(aEventName, onEvent, true);
|
||||
if (aPreventDefault) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitForBrowserContextMenu(aCallback) {
|
||||
waitForPopupShown(gBrowser.selectedBrowser.contextMenu, aCallback);
|
||||
}
|
||||
|
@ -106,3 +121,16 @@ function doOnloadOnce(aCallback) {
|
|||
gBrowser.addEventListener("load", doOnloadOnceListener, true);
|
||||
registerCleanupFunction(removeDoOnloadOnceListener);
|
||||
}
|
||||
|
||||
function* promiseOnLoad() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
gBrowser.addEventListener("load", function onLoadListener(aEvent) {
|
||||
info("onLoadListener: " + aEvent.originalTarget.location);
|
||||
gBrowser.removeEventListener("load", onLoadListener, true);
|
||||
deferred.resolve(aEvent);
|
||||
}, true);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,13 +22,13 @@ const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
|
|||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
|
||||
|
||||
/**
|
||||
* DevTools is a class that represents a set of developer tools, it holds a
|
||||
* set of tools and keeps track of open toolboxes in the browser.
|
||||
*/
|
||||
this.DevTools = function DevTools() {
|
||||
this._tools = new Map(); // Map<toolId, tool>
|
||||
this._themes = new Map(); // Map<themeId, theme>
|
||||
this._toolboxes = new Map(); // Map<target, toolbox>
|
||||
|
||||
// destroy() is an observer's handler so we need to preserve context.
|
||||
|
@ -229,6 +229,136 @@ DevTools.prototype = {
|
|||
return definitions.sort(this.ordinalSort);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a new theme for developer tools toolbox.
|
||||
*
|
||||
* A definition is a light object that holds various information about a
|
||||
* theme.
|
||||
*
|
||||
* Each themeDefinition has the following properties:
|
||||
* - id: Unique identifier for this theme (string|required)
|
||||
* - label: Localized name for the theme to be displayed to the user
|
||||
* (string|required)
|
||||
* - stylesheets: Array of URLs pointing to a CSS document(s) containing
|
||||
* the theme style rules (array|required)
|
||||
* - classList: Array of class names identifying the theme within a document.
|
||||
* These names are set to document element when applying
|
||||
* the theme (array|required)
|
||||
* - onApply: Function that is executed by the framework when the theme
|
||||
* is applied. The function takes the current iframe window
|
||||
* and the previous theme id as arguments (function)
|
||||
* - onUnapply: Function that is executed by the framework when the theme
|
||||
* is unapplied. The function takes the current iframe window
|
||||
* and the new theme id as arguments (function)
|
||||
*/
|
||||
registerTheme: function DT_registerTheme(themeDefinition) {
|
||||
let themeId = themeDefinition.id;
|
||||
|
||||
if (!themeId) {
|
||||
throw new Error("Invalid theme id");
|
||||
}
|
||||
|
||||
if (this._themes.get(themeId)) {
|
||||
throw new Error("Theme with the same id is already registered");
|
||||
}
|
||||
|
||||
this._themes.set(themeId, themeDefinition);
|
||||
|
||||
this.emit("theme-registered", themeId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an existing theme from the list of registered themes.
|
||||
* Needed so that add-ons can remove themselves when they are deactivated
|
||||
*
|
||||
* @param {string|object} theme
|
||||
* Definition or the id of the theme to unregister.
|
||||
*/
|
||||
unregisterTheme: function DT_unregisterTheme(theme) {
|
||||
let themeId = null;
|
||||
if (typeof theme == "string") {
|
||||
themeId = theme;
|
||||
theme = this._themes.get(theme);
|
||||
}
|
||||
else {
|
||||
themeId = theme.id;
|
||||
}
|
||||
|
||||
let currTheme = Services.prefs.getCharPref("devtools.theme");
|
||||
|
||||
// Change the current theme if it's being dynamically removed together
|
||||
// with the owner (bootstrapped) extension.
|
||||
// But, do not change it if the application is just shutting down.
|
||||
if (!Services.startup.shuttingDown && theme.id == currTheme) {
|
||||
Services.prefs.setCharPref("devtools.theme", "light");
|
||||
|
||||
let data = {
|
||||
pref: "devtools.theme",
|
||||
newValue: "light",
|
||||
oldValue: currTheme
|
||||
};
|
||||
|
||||
gDevTools.emit("pref-changed", data);
|
||||
|
||||
this.emit("theme-unregistered", theme);
|
||||
}
|
||||
|
||||
this._themes.delete(themeId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a theme definition if it exists.
|
||||
*
|
||||
* @param {string} themeId
|
||||
* The id of the theme
|
||||
*
|
||||
* @return {ThemeDefinition|null} theme
|
||||
* The ThemeDefinition for the id or null.
|
||||
*/
|
||||
getThemeDefinition: function DT_getThemeDefinition(themeId) {
|
||||
let theme = this._themes.get(themeId);
|
||||
if (!theme) {
|
||||
return null;
|
||||
}
|
||||
return theme;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get map of registered themes.
|
||||
*
|
||||
* @return {Map} themes
|
||||
* A map of the the theme definitions registered in this instance
|
||||
*/
|
||||
getThemeDefinitionMap: function DT_getThemeDefinitionMap() {
|
||||
let themes = new Map();
|
||||
|
||||
for (let [id, definition] of this._themes) {
|
||||
if (this.getThemeDefinition(id)) {
|
||||
themes.set(id, definition);
|
||||
}
|
||||
}
|
||||
|
||||
return themes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get registered themes definitions sorted by ordinal value.
|
||||
*
|
||||
* @return {Array} themes
|
||||
* A sorted array of the theme definitions registered in this instance
|
||||
*/
|
||||
getThemeDefinitionArray: function DT_getThemeDefinitionArray() {
|
||||
let definitions = [];
|
||||
|
||||
for (let [id, definition] of this._themes) {
|
||||
if (this.getThemeDefinition(id)) {
|
||||
definitions.push(definition);
|
||||
}
|
||||
}
|
||||
|
||||
return definitions.sort(this.ordinalSort);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show a Toolbox for a target (either by creating a new one, or if a toolbox
|
||||
* already exists for the target, by bring to the front the existing one)
|
||||
|
|
|
@ -5,6 +5,7 @@ support-files =
|
|||
browser_toolbox_options_disable_js_iframe.html
|
||||
browser_toolbox_options_disable_cache.sjs
|
||||
head.js
|
||||
doc_theme.css
|
||||
|
||||
[browser_devtools_api.js]
|
||||
[browser_dynamic_tool_enabling.js]
|
||||
|
@ -32,6 +33,7 @@ skip-if = e10s # Bug 1030318
|
|||
[browser_toolbox_window_title_changes.js]
|
||||
[browser_toolbox_zoom.js]
|
||||
[browser_toolbox_custom_host.js]
|
||||
[browser_toolbox_theme_registration.js]
|
||||
|
||||
# We want this test to run for mochitest-dt as well, so we include it here:
|
||||
[../../../base/content/test/general/browser_parsable_css.js]
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const CHROME_URL = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
|
||||
|
||||
let toolbox;
|
||||
|
||||
function test()
|
||||
{
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
|
||||
gDevTools.showToolbox(target).then(testRegister);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,test for dynamically registering and unregistering themes";
|
||||
}
|
||||
|
||||
function testRegister(aToolbox)
|
||||
{
|
||||
toolbox = aToolbox
|
||||
gDevTools.once("theme-registered", themeRegistered);
|
||||
|
||||
gDevTools.registerTheme({
|
||||
id: "test-theme",
|
||||
label: "Test theme",
|
||||
stylesheets: [CHROME_URL + "doc_theme.css"],
|
||||
classList: ["theme-test"],
|
||||
});
|
||||
}
|
||||
|
||||
function themeRegistered(event, themeId)
|
||||
{
|
||||
is(themeId, "test-theme", "theme-registered event handler sent theme id");
|
||||
|
||||
ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
|
||||
|
||||
// Test that new theme appears in the Options panel
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "options").then(() => {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let doc = panel.panelWin.frameElement.contentDocument;
|
||||
let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
|
||||
|
||||
ok(themeOption, "new theme exists in the Options panel");
|
||||
|
||||
// Apply the new theme.
|
||||
applyTheme();
|
||||
});
|
||||
}
|
||||
|
||||
function applyTheme()
|
||||
{
|
||||
let panelWin = toolbox.getCurrentPanel().panelWin;
|
||||
let doc = panelWin.frameElement.contentDocument;
|
||||
let testThemeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
|
||||
let lightThemeOption = doc.querySelector("#devtools-theme-box > radio[value=light]");
|
||||
|
||||
let color = panelWin.getComputedStyle(testThemeOption).color;
|
||||
isnot(color, "rgb(255, 0, 0)", "style unapplied");
|
||||
|
||||
// Select test theme.
|
||||
testThemeOption.click();
|
||||
|
||||
let color = panelWin.getComputedStyle(testThemeOption).color;
|
||||
is(color, "rgb(255, 0, 0)", "style applied");
|
||||
|
||||
// Select light theme
|
||||
lightThemeOption.click();
|
||||
|
||||
let color = panelWin.getComputedStyle(testThemeOption).color;
|
||||
isnot(color, "rgb(255, 0, 0)", "style unapplied");
|
||||
|
||||
// Select test theme again.
|
||||
testThemeOption.click();
|
||||
|
||||
// Then unregister the test theme.
|
||||
testUnregister();
|
||||
}
|
||||
|
||||
function testUnregister()
|
||||
{
|
||||
gDevTools.unregisterTheme("test-theme");
|
||||
|
||||
ok(!gDevTools.getThemeDefinitionMap().has("test-theme"), "theme removed from map");
|
||||
|
||||
let panelWin = toolbox.getCurrentPanel().panelWin;
|
||||
let doc = panelWin.frameElement.contentDocument;
|
||||
let themeBox = doc.querySelector("#devtools-theme-box");
|
||||
|
||||
// The default light theme must be selected now.
|
||||
is(themeBox.selectedItem, themeBox.querySelector("[value=light]"),
|
||||
"theme light must be selected");
|
||||
|
||||
// Make sure the tab-attaching process is done before we destroy the toolbox.
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let actor = target.activeTab.actor;
|
||||
target.client.attachTab(actor, (response) => {
|
||||
cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup()
|
||||
{
|
||||
toolbox.destroy().then(function() {
|
||||
toolbox = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.theme-test #devtools-theme-box radio {
|
||||
color: red !important;
|
||||
}
|
|
@ -75,6 +75,8 @@ function OptionsPanel(iframeWindow, toolbox) {
|
|||
this.isReady = false;
|
||||
|
||||
this._prefChanged = this._prefChanged.bind(this);
|
||||
this._themeRegistered = this._themeRegistered.bind(this);
|
||||
this._themeUnregistered = this._themeUnregistered.bind(this);
|
||||
|
||||
this._addListeners();
|
||||
|
||||
|
@ -101,7 +103,9 @@ OptionsPanel.prototype = {
|
|||
return targetPromise.then(() => {
|
||||
this.setupToolsList();
|
||||
this.setupToolbarButtonsList();
|
||||
this.setupThemeList();
|
||||
this.populatePreferences();
|
||||
this.updateDefaultTheme();
|
||||
|
||||
this._disableJSClicked = this._disableJSClicked.bind(this);
|
||||
|
||||
|
@ -119,10 +123,14 @@ OptionsPanel.prototype = {
|
|||
|
||||
_addListeners: function() {
|
||||
gDevTools.on("pref-changed", this._prefChanged);
|
||||
gDevTools.on("theme-registered", this._themeRegistered);
|
||||
gDevTools.on("theme-unregistered", this._themeUnregistered);
|
||||
},
|
||||
|
||||
_removeListeners: function() {
|
||||
gDevTools.off("pref-changed", this._prefChanged);
|
||||
gDevTools.off("theme-registered", this._themeRegistered);
|
||||
gDevTools.off("theme-unregistered", this._themeUnregistered);
|
||||
},
|
||||
|
||||
_prefChanged: function(event, data) {
|
||||
|
@ -132,6 +140,22 @@ OptionsPanel.prototype = {
|
|||
|
||||
cbx.checked = cacheDisabled;
|
||||
}
|
||||
else if (data.pref === "devtools.theme") {
|
||||
this.updateCurrentTheme();
|
||||
}
|
||||
},
|
||||
|
||||
_themeRegistered: function(event, themeId) {
|
||||
this.setupThemeList();
|
||||
},
|
||||
|
||||
_themeUnregistered: function(event, theme) {
|
||||
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
|
||||
let themeOption = themeBox.querySelector("[value=" + theme.id + "]");
|
||||
|
||||
if (themeOption) {
|
||||
themeBox.removeChild(themeOption);
|
||||
}
|
||||
},
|
||||
|
||||
setupToolbarButtonsList: function() {
|
||||
|
@ -229,6 +253,26 @@ OptionsPanel.prototype = {
|
|||
this.panelWin.focus();
|
||||
},
|
||||
|
||||
setupThemeList: function() {
|
||||
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
|
||||
themeBox.textContent = "";
|
||||
|
||||
let createThemeOption = theme => {
|
||||
let radio = this.panelDoc.createElement("radio");
|
||||
radio.setAttribute("value", theme.id);
|
||||
radio.setAttribute("label", theme.label);
|
||||
return radio;
|
||||
};
|
||||
|
||||
// Populating the default theme list
|
||||
let themes = gDevTools.getThemeDefinitionArray();
|
||||
for (let theme of themes) {
|
||||
themeBox.appendChild(createThemeOption(theme));
|
||||
}
|
||||
|
||||
this.updateCurrentTheme();
|
||||
},
|
||||
|
||||
populatePreferences: function() {
|
||||
let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
|
||||
for (let checkbox of prefCheckboxes) {
|
||||
|
@ -258,9 +302,13 @@ OptionsPanel.prototype = {
|
|||
pref: this.getAttribute("data-pref"),
|
||||
newValue: this.selectedItem.getAttribute("value")
|
||||
};
|
||||
|
||||
data.oldValue = GetPref(data.pref);
|
||||
SetPref(data.pref, data.newValue);
|
||||
gDevTools.emit("pref-changed", data);
|
||||
|
||||
if (data.newValue != data.oldValue) {
|
||||
gDevTools.emit("pref-changed", data);
|
||||
}
|
||||
}.bind(radiogroup));
|
||||
}
|
||||
let prefMenulists = this.panelDoc.querySelectorAll("menulist[data-pref]");
|
||||
|
@ -292,6 +340,25 @@ OptionsPanel.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
updateDefaultTheme: function() {
|
||||
// Make sure a theme is set in case the previous one coming from
|
||||
// an extension isn't available anymore.
|
||||
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
|
||||
if (themeBox.selectedIndex == -1) {
|
||||
themeBox.selectedItem = themeBox.querySelector("[value=light]");
|
||||
}
|
||||
},
|
||||
|
||||
updateCurrentTheme: function() {
|
||||
let currentTheme = GetPref("devtools.theme");
|
||||
let themeBox = this.panelDoc.getElementById("devtools-theme-box");
|
||||
let themeOption = themeBox.querySelector("[value=" + currentTheme + "]");
|
||||
|
||||
if (themeOption) {
|
||||
themeBox.selectedItem = themeOption;
|
||||
}
|
||||
},
|
||||
|
||||
_populateDisableJSCheckbox: function() {
|
||||
let cbx = this.panelDoc.getElementById("devtools-disable-javascript");
|
||||
cbx.checked = !this._origJavascriptEnabled;
|
||||
|
|
|
@ -33,8 +33,6 @@
|
|||
class="options-groupbox"
|
||||
data-pref="devtools.theme"
|
||||
orient="horizontal">
|
||||
<radio value="light" label="&options.lightTheme.label;"/>
|
||||
<radio value="dark" label="&options.darkTheme.label;"/>
|
||||
</radiogroup>
|
||||
<label>&options.commonPrefs.label;</label>
|
||||
<vbox id="commonprefs-options" class="options-groupbox">
|
||||
|
|
|
@ -357,6 +357,31 @@ for (let definition of defaultTools) {
|
|||
gDevTools.registerTool(definition);
|
||||
}
|
||||
|
||||
Tools.darkTheme = {
|
||||
id: "dark",
|
||||
label: l10n("options.darkTheme.label", toolboxStrings),
|
||||
ordinal: 1,
|
||||
stylesheets: ["chrome://browser/skin/devtools/dark-theme.css"],
|
||||
classList: ["theme-dark"],
|
||||
};
|
||||
|
||||
Tools.lightTheme = {
|
||||
id: "light",
|
||||
label: l10n("options.lightTheme.label", toolboxStrings),
|
||||
ordinal: 2,
|
||||
stylesheets: ["chrome://browser/skin/devtools/light-theme.css"],
|
||||
classList: ["theme-light"],
|
||||
};
|
||||
|
||||
let defaultThemes = [
|
||||
Tools.darkTheme,
|
||||
Tools.lightTheme,
|
||||
];
|
||||
|
||||
for (let definition of defaultThemes) {
|
||||
gDevTools.registerTheme(definition);
|
||||
}
|
||||
|
||||
var unloadObserver = {
|
||||
observe: function(subject, topic, data) {
|
||||
if (subject.wrappedJSObject === require("@loader/unload")) {
|
||||
|
@ -364,6 +389,9 @@ var unloadObserver = {
|
|||
for (let definition of gDevTools.getToolDefinitionArray()) {
|
||||
gDevTools.unregisterTool(definition.id);
|
||||
}
|
||||
for (let definition of gDevTools.getThemeDefinitionArray()) {
|
||||
gDevTools.unregisterTheme(definition.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let { utils: Cu, interfaces: Ci } = Components;
|
||||
|
||||
addMessageListener("devtools:test:history", function ({ data }) {
|
||||
content.history[data.direction]();
|
||||
});
|
||||
|
@ -16,3 +18,11 @@ addMessageListener("devtools:test:reload", function ({ data }) {
|
|||
data = data || {};
|
||||
content.location.reload(data.forceget);
|
||||
});
|
||||
|
||||
addMessageListener("devtools:test:forceCC", function () {
|
||||
let DOMWindowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
DOMWindowUtils.cycleCollect();
|
||||
DOMWindowUtils.garbageCollect();
|
||||
DOMWindowUtils.garbageCollect();
|
||||
});
|
||||
|
|
|
@ -24,24 +24,35 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (oldTheme && newTheme != oldTheme) {
|
||||
StylesheetUtils.removeSheet(
|
||||
window,
|
||||
DEVTOOLS_SKIN_URL + oldTheme + "-theme.css",
|
||||
"author"
|
||||
);
|
||||
let oldThemeDef = gDevTools.getThemeDefinition(oldTheme);
|
||||
let newThemeDef = gDevTools.getThemeDefinition(newTheme);
|
||||
|
||||
// Unload all theme stylesheets related to the old theme.
|
||||
if (oldThemeDef) {
|
||||
for (let url of oldThemeDef.stylesheets) {
|
||||
StylesheetUtils.removeSheet(window, url, "author");
|
||||
}
|
||||
}
|
||||
|
||||
StylesheetUtils.loadSheet(
|
||||
window,
|
||||
DEVTOOLS_SKIN_URL + newTheme + "-theme.css",
|
||||
"author"
|
||||
);
|
||||
// Load all stylesheets associated with the new theme.
|
||||
let newThemeDef = gDevTools.getThemeDefinition(newTheme);
|
||||
|
||||
// Floating scrollbars à la osx
|
||||
// The theme might not be available anymore (e.g. uninstalled)
|
||||
// Use the default one.
|
||||
if (!newThemeDef) {
|
||||
newThemeDef = gDevTools.getThemeDefinition("light");
|
||||
}
|
||||
|
||||
for (let url of newThemeDef.stylesheets) {
|
||||
StylesheetUtils.loadSheet(window, url, "author");
|
||||
}
|
||||
|
||||
// Floating scroll-bars like in OSX
|
||||
let hiddenDOMWindow = Cc["@mozilla.org/appshell/appShellService;1"]
|
||||
.getService(Ci.nsIAppShellService)
|
||||
.hiddenDOMWindow;
|
||||
|
||||
// TODO: extensions might want to customize scrollbar styles too.
|
||||
if (!hiddenDOMWindow.matchMedia("(-moz-overlay-scrollbars)").matches) {
|
||||
let scrollbarsUrl = Services.io.newURI(
|
||||
DEVTOOLS_SKIN_URL + "floating-scrollbars-light.css", null, null);
|
||||
|
@ -62,8 +73,26 @@
|
|||
forceStyle();
|
||||
}
|
||||
|
||||
documentElement.classList.remove("theme-" + oldTheme);
|
||||
documentElement.classList.add("theme-" + newTheme);
|
||||
if (oldThemeDef) {
|
||||
for (let name of oldThemeDef.classList) {
|
||||
documentElement.classList.remove(name);
|
||||
}
|
||||
|
||||
if (oldThemeDef.onUnapply) {
|
||||
oldThemeDef.onUnapply(window, newTheme);
|
||||
}
|
||||
}
|
||||
|
||||
for (let name of newThemeDef.classList) {
|
||||
documentElement.classList.add(name);
|
||||
}
|
||||
|
||||
if (newThemeDef.onApply) {
|
||||
newThemeDef.onApply(window, oldTheme);
|
||||
}
|
||||
|
||||
// Final notification for further theme-switching related logic.
|
||||
gDevTools.emit("theme-switched", window, newTheme, oldTheme);
|
||||
}
|
||||
|
||||
function handlePrefChange(event, data) {
|
||||
|
|
|
@ -10,18 +10,23 @@ support-files =
|
|||
doc_connect-toggle.html
|
||||
doc_connect-param.html
|
||||
doc_connect-multi-param.html
|
||||
doc_change-param.html
|
||||
440hz_sine.ogg
|
||||
head.js
|
||||
|
||||
[browser_audionode-actor-get-set-param.js]
|
||||
[browser_audionode-actor-get-type.js]
|
||||
[browser_audionode-actor-get-param-flags.js]
|
||||
[browser_audionode-actor-get-params-01.js]
|
||||
[browser_audionode-actor-get-params-02.js]
|
||||
[browser_audionode-actor-get-param-flags.js]
|
||||
[browser_audionode-actor-get-set-param.js]
|
||||
[browser_audionode-actor-get-type.js]
|
||||
[browser_audionode-actor-is-source.js]
|
||||
[browser_webaudio-actor-simple.js]
|
||||
[browser_webaudio-actor-destroy-node.js]
|
||||
|
||||
[browser_webaudio-actor-change-params-01.js]
|
||||
[browser_webaudio-actor-change-params-02.js]
|
||||
[browser_webaudio-actor-change-params-03.js]
|
||||
[browser_webaudio-actor-connect-param.js]
|
||||
[browser_webaudio-actor-destroy-node.js]
|
||||
[browser_webaudio-actor-simple.js]
|
||||
|
||||
[browser_wa_destroy-node-01.js]
|
||||
|
||||
|
@ -48,4 +53,5 @@ support-files =
|
|||
# [browser_wa_properties-view-edit-02.js]
|
||||
# Disabled for too many intermittents bug 1010423
|
||||
[browser_wa_properties-view-params.js]
|
||||
[browser_wa_properties-view-change-params.js]
|
||||
[browser_wa_properties-view-params-objects.js]
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that params view correctly updates changed parameters
|
||||
* when source code updates them, as well as CHANGE_PARAM events.
|
||||
*/
|
||||
|
||||
function spawnTest() {
|
||||
let [target, debuggee, panel] = yield initWebAudioEditor(CHANGE_PARAM_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, WebAudioInspectorView } = panelWin;
|
||||
let gVars = WebAudioInspectorView._propsView;
|
||||
|
||||
// Set parameter polling to 20ms for tests
|
||||
panelWin.PARAM_POLLING_FREQUENCY = 20;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
getN(gFront, "create-node", 3),
|
||||
waitForGraphRendered(panelWin, 3, 0)
|
||||
]);
|
||||
|
||||
let oscId = actors[1].actorID;
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, oscId));
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
|
||||
// Yield twice so we get a diff
|
||||
yield once(panelWin, EVENTS.CHANGE_PARAM);
|
||||
let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
|
||||
is(args.actorID, oscId, "EVENTS.CHANGE_PARAM has correct `actorID`");
|
||||
ok(args.oldValue < args.newValue, "EVENTS.CHANGE_PARAM has correct `newValue` and `oldValue`");
|
||||
is(args.param, "detune", "EVENTS.CHANGE_PARAM has correct `param`");
|
||||
|
||||
let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
|
||||
checkVariableView(gVars, 0, { "detune": args.newValue }, "`detune` parameter updated.");
|
||||
let [[_, args]] = yield getSpread(panelWin, EVENTS.CHANGE_PARAM);
|
||||
checkVariableView(gVars, 0, { "detune": args.newValue }, "`detune` parameter updated.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test WebAudioActor `change-param` events and front.[en|dis]ableChangeParamEvents
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(CHANGE_PARAM_URL);
|
||||
let [_, nodes] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
getN(front, "create-node", 3)
|
||||
]);
|
||||
|
||||
let osc = nodes[1];
|
||||
let eventCount = 0;
|
||||
|
||||
yield front.enableChangeParamEvents(osc, 20);
|
||||
|
||||
front.on("change-param", onChangeParam);
|
||||
|
||||
yield getN(front, "change-param", 3);
|
||||
yield front.disableChangeParamEvents();
|
||||
|
||||
let currEventCount = eventCount;
|
||||
|
||||
// Be flexible here incase we get an extra counter before the listener is turned off
|
||||
ok(eventCount >= 3, "Calling `enableChangeParamEvents` should allow front to emit `change-param`.");
|
||||
|
||||
yield wait(100);
|
||||
|
||||
ok((eventCount - currEventCount) <= 2, "Calling `disableChangeParamEvents` should turn off the listener.");
|
||||
|
||||
front.off("change-param", onChangeParam);
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function onChangeParam ({ newValue, oldValue, param, actorID }) {
|
||||
is(actorID, osc.actorID, "correct `actorID` in `change-param`.");
|
||||
is(param, "detune", "correct `param` property in `change-param`.");
|
||||
ok(newValue > oldValue,
|
||||
"correct `newValue` (" + newValue + ") and `oldValue` (" + oldValue + ") in `change-param`");
|
||||
eventCount++;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that listening to param change polling does not break when the AudioNode is collected.
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(DESTROY_NODES_URL);
|
||||
let waitUntilDestroyed = getN(front, "destroy-node", 10);
|
||||
let [_, nodes] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
getN(front, "create-node", 13)
|
||||
]);
|
||||
|
||||
let bufferNode = nodes[6];
|
||||
|
||||
yield front.enableChangeParamEvents(bufferNode, 20);
|
||||
|
||||
front.on("change-param", onChangeParam);
|
||||
|
||||
forceCC();
|
||||
|
||||
yield waitUntilDestroyed;
|
||||
yield wait(50);
|
||||
|
||||
front.off("change-param", onChangeParam);
|
||||
|
||||
ok(true, "listening to `change-param` on a dead node doesn't throw.");
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function onChangeParam (args) {
|
||||
ok(false, "`change-param` should not be emitted on a node that hasn't changed params or is dead.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test WebAudioActor `change-param` events on special types.
|
||||
*/
|
||||
|
||||
function spawnTest () {
|
||||
let [target, debuggee, front] = yield initBackend(CHANGE_PARAM_URL);
|
||||
let [_, nodes] = yield Promise.all([
|
||||
front.setup({ reload: true }),
|
||||
getN(front, "create-node", 3)
|
||||
]);
|
||||
|
||||
let shaper = nodes[2];
|
||||
let eventCount = 0;
|
||||
|
||||
yield front.enableChangeParamEvents(shaper, 20);
|
||||
|
||||
let onChange = once(front, "change-param");
|
||||
|
||||
shaper.setParam("curve", null);
|
||||
|
||||
let { newValue, oldValue } = yield onChange;
|
||||
|
||||
is(oldValue.type, "object", "`oldValue` should be an object.");
|
||||
is(oldValue.class, "Float32Array", "`oldValue` should be of class Float32Array.");
|
||||
is(newValue.type, "null", "`newValue` should be null.");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Web Audio Editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
let ctx = new AudioContext();
|
||||
let osc = ctx.createOscillator();
|
||||
let shaperNode = ctx.createWaveShaper();
|
||||
let detuneVal = 0;
|
||||
shaperNode.curve = new Float32Array(65536);
|
||||
setInterval(() => osc.detune.value = ++detuneVal, 10);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -19,7 +19,9 @@ let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.j
|
|||
|
||||
let { WebAudioFront } = devtools.require("devtools/server/actors/webaudio");
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
let mm = null;
|
||||
|
||||
const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js";
|
||||
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/webaudioeditor/test/";
|
||||
const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
|
||||
const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
|
||||
|
@ -30,6 +32,7 @@ const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
|
|||
const CONNECT_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
|
||||
const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
|
||||
const CONNECT_MULTI_PARAM_URL = EXAMPLE_URL + "doc_connect-multi-param.html";
|
||||
const CHANGE_PARAM_URL = EXAMPLE_URL + "doc_change-param.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
@ -133,6 +136,8 @@ function initBackend(aUrl) {
|
|||
yield target.makeRemote();
|
||||
|
||||
let front = new WebAudioFront(target.client, target.form);
|
||||
|
||||
loadFrameScripts();
|
||||
return [target, debuggee, front];
|
||||
});
|
||||
}
|
||||
|
@ -150,6 +155,8 @@ function initWebAudioEditor(aUrl) {
|
|||
Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "webaudioeditor");
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
|
||||
loadFrameScripts();
|
||||
return [target, debuggee, panel];
|
||||
});
|
||||
}
|
||||
|
@ -387,9 +394,12 @@ function countGraphObjects (win) {
|
|||
* Forces cycle collection and GC, used in AudioNode destruction tests.
|
||||
*/
|
||||
function forceCC () {
|
||||
SpecialPowers.DOMWindowUtils.cycleCollect();
|
||||
SpecialPowers.DOMWindowUtils.garbageCollect();
|
||||
SpecialPowers.DOMWindowUtils.garbageCollect();
|
||||
mm.sendAsyncMessage("devtools:test:forceCC");
|
||||
}
|
||||
|
||||
function loadFrameScripts () {
|
||||
mm = gBrowser.selectedBrowser.messageManager;
|
||||
mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,9 +20,10 @@ const STRINGS_URI = "chrome://browser/locale/devtools/webaudioeditor.properties"
|
|||
const L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
const Telemetry = require("devtools/shared/telemetry");
|
||||
const telemetry = new Telemetry();
|
||||
|
||||
let { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
|
||||
let PARAM_POLLING_FREQUENCY = 1000;
|
||||
|
||||
// The panel's window global is an EventEmitter firing the following events:
|
||||
const EVENTS = {
|
||||
// Fired when the first AudioNode has been created, signifying
|
||||
|
@ -173,6 +174,8 @@ let WebAudioEditorController = {
|
|||
telemetry.toolOpened("webaudioeditor");
|
||||
this._onTabNavigated = this._onTabNavigated.bind(this);
|
||||
this._onThemeChange = this._onThemeChange.bind(this);
|
||||
this._onSelectNode = this._onSelectNode.bind(this);
|
||||
this._onChangeParam = this._onChangeParam.bind(this);
|
||||
gTarget.on("will-navigate", this._onTabNavigated);
|
||||
gTarget.on("navigate", this._onTabNavigated);
|
||||
gFront.on("start-context", this._onStartContext);
|
||||
|
@ -194,12 +197,15 @@ let WebAudioEditorController = {
|
|||
window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
|
||||
window.on(EVENTS.DESTROY_NODE, this._onUpdatedContext);
|
||||
window.on(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
|
||||
|
||||
// Set up a controller for managing parameter changes per audio node
|
||||
window.on(EVENTS.UI_SELECT_NODE, this._onSelectNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove events emitted by the current tab target.
|
||||
*/
|
||||
destroy: function() {
|
||||
destroy: Task.async(function* () {
|
||||
telemetry.toolClosed("webaudioeditor");
|
||||
gTarget.off("will-navigate", this._onTabNavigated);
|
||||
gTarget.off("navigate", this._onTabNavigated);
|
||||
|
@ -215,8 +221,11 @@ let WebAudioEditorController = {
|
|||
window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
|
||||
window.off(EVENTS.DESTROY_NODE, this._onUpdatedContext);
|
||||
window.off(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
|
||||
window.off(EVENTS.UI_SELECT_NODE, this._onSelectNode);
|
||||
gDevTools.off("pref-changed", this._onThemeChange);
|
||||
},
|
||||
|
||||
yield gFront.disableChangeParamEvents();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called when page is reloaded to show the reload notice and waiting
|
||||
|
@ -345,9 +354,21 @@ let WebAudioEditorController = {
|
|||
/**
|
||||
* Called when a node param is changed.
|
||||
*/
|
||||
_onChangeParam: function({ actor, param, value }) {
|
||||
window.emit(EVENTS.CHANGE_PARAM, getViewNodeByActor(actor), param, value);
|
||||
}
|
||||
_onChangeParam: function (args) {
|
||||
window.emit(EVENTS.CHANGE_PARAM, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called on UI_SELECT_NODE, used to manage
|
||||
* `change-param` events on that node.
|
||||
*/
|
||||
_onSelectNode: function (_, id) {
|
||||
let node = getViewNodeById(id);
|
||||
|
||||
if (node && node.actor) {
|
||||
gFront.enableChangeParamEvents(node.actor, PARAM_POLLING_FREQUENCY);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -377,6 +377,7 @@ let WebAudioInspectorView = {
|
|||
this._onNodeSelect = this._onNodeSelect.bind(this);
|
||||
this._onTogglePaneClick = this._onTogglePaneClick.bind(this);
|
||||
this._onDestroyNode = this._onDestroyNode.bind(this);
|
||||
this._onChangeParam = this._onChangeParam.bind(this);
|
||||
|
||||
this._inspectorPaneToggleButton.addEventListener("mousedown", this._onTogglePaneClick, false);
|
||||
this._propsView = new VariablesView($("#properties-tabpanel-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
|
||||
|
@ -384,6 +385,7 @@ let WebAudioInspectorView = {
|
|||
|
||||
window.on(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
|
||||
window.on(EVENTS.DESTROY_NODE, this._onDestroyNode);
|
||||
window.on(EVENTS.CHANGE_PARAM, this._onChangeParam);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -393,6 +395,7 @@ let WebAudioInspectorView = {
|
|||
this._inspectorPaneToggleButton.removeEventListener("mousedown", this._onTogglePaneClick);
|
||||
window.off(EVENTS.UI_SELECT_NODE, this._onNodeSelect);
|
||||
window.off(EVENTS.DESTROY_NODE, this._onDestroyNode);
|
||||
window.off(EVENTS.CHANGE_PARAM, this._onChangeParam);
|
||||
|
||||
this._inspectorPane = null;
|
||||
this._inspectorPaneToggleButton = null;
|
||||
|
@ -612,7 +615,22 @@ let WebAudioInspectorView = {
|
|||
if (this._currentNode && this._currentNode.id === id) {
|
||||
this.setCurrentAudioNode(null);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when `CHANGE_PARAM` is fired. We should ensure that this event is
|
||||
* for the same node that is currently selected. We check the existence
|
||||
* of each part of the scope to make sure that if this event was fired
|
||||
* during a VariablesView rebuild, then we just ignore it.
|
||||
*/
|
||||
_onChangeParam: function (_, { param, newValue, oldValue, actorID }) {
|
||||
if (!this._currentNode || this._currentNode.actor.actorID !== actorID) return;
|
||||
let scope = this._getAudioPropertiesScope();
|
||||
if (!scope) return;
|
||||
let property = scope.get(param);
|
||||
if (!property) return;
|
||||
property.setGrip(newValue);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<menuitem command="cmd_newApp" accesskey="&projectMenu_newApp_accesskey;"/>
|
||||
<menuitem command="cmd_importPackagedApp" accesskey="&projectMenu_importPackagedApp_accesskey;"/>
|
||||
<menuitem command="cmd_importHostedApp" accesskey="&projectMenu_importHostedApp_accesskey;"/>
|
||||
<menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accessley;"/>
|
||||
<menuitem command="cmd_showProjectPanel" key="key_showProjectPanel" label="&projectMenu_selectApp_label;" accesskey="&projectMenu_selectApp_accesskey;"/>
|
||||
<menuseparator/>
|
||||
<menuitem command="cmd_play" key="key_play" label="&projectMenu_play_label;" accesskey="&projectMenu_play_accesskey;"/>
|
||||
<menuitem command="cmd_stop" accesskey="&projectMenu_stop_accesskey;"/>
|
||||
|
|
|
@ -359,7 +359,7 @@ Experiments.Experiments = function (policy=new Experiments.Policy()) {
|
|||
// crashes. For forensics purposes, keep the last few log
|
||||
// messages in memory and upload them in case of crash.
|
||||
this._forensicsLogs = [];
|
||||
this._forensicsLogs.length = 3;
|
||||
this._forensicsLogs.length = 10;
|
||||
this._log = Object.create(log);
|
||||
this._log.log = (level, string, params) => {
|
||||
this._forensicsLogs.shift();
|
||||
|
|
|
@ -114,8 +114,6 @@
|
|||
- the heading of the radiobox corresponding to the theme of the developer
|
||||
- tools. -->
|
||||
<!ENTITY options.selectDevToolsTheme.label "Choose DevTools theme:">
|
||||
<!ENTITY options.darkTheme.label "Dark theme">
|
||||
<!ENTITY options.lightTheme.label "Light theme">
|
||||
|
||||
<!-- LOCALIZATION NOTE (options.webconsole.label): This is the label for the
|
||||
- heading of the group of Web Console preferences in the options panel. -->
|
||||
|
|
|
@ -70,3 +70,11 @@ browserConsoleCmd.commandkey=j
|
|||
# LOCALIZATION NOTE (pickButton.tooltip)
|
||||
# This is the tooltip of the pick button in the toolbox toolbar
|
||||
pickButton.tooltip=Pick an element from the page
|
||||
|
||||
# LOCALIZATION NOTE (options.darkTheme.label)
|
||||
# Used as a label for dark theme
|
||||
options.darkTheme.label=Dark theme
|
||||
|
||||
# LOCALIZATION NOTE (options.lightTheme.label)
|
||||
# Used as a label for light theme
|
||||
options.lightTheme.label=Light theme
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<!ENTITY projectMenu_importHostedApp_label "Open Hosted App…">
|
||||
<!ENTITY projectMenu_importHostedApp_accesskey "H">
|
||||
<!ENTITY projectMenu_selectApp_label "Open App…">
|
||||
<!ENTITY projectMenu_selectApp_accessley "S">
|
||||
<!ENTITY projectMenu_selectApp_accesskey "O">
|
||||
<!ENTITY projectMenu_play_label "Install and Run">
|
||||
<!ENTITY projectMenu_play_accesskey "I">
|
||||
<!ENTITY projectMenu_stop_label "Stop App">
|
||||
|
|
|
@ -40,6 +40,10 @@ error_cantFetchAddonsJSON=Can't fetch the add-on list: %S
|
|||
|
||||
addons_stable=stable
|
||||
addons_unstable=unstable
|
||||
# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
|
||||
# a given simulator version in the "Manage Simulators" pane. %1$S: Firefox OS
|
||||
# version in the simulator, ex. 1.3. %2$S: Simulator stability label, ex.
|
||||
# "stable" or "unstable".
|
||||
addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
|
||||
addons_install_button=install
|
||||
addons_uninstall_button=uninstall
|
||||
|
|
|
@ -77,6 +77,10 @@ menu[disabled="true"].subviewbutton > .menu-right {
|
|||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.subviewbutton > .toolbarbutton-icon {
|
||||
-moz-margin-end: 5px !important;
|
||||
}
|
||||
|
|
|
@ -126,6 +126,10 @@ menu[disabled="true"].subviewbutton > .menu-right {
|
|||
-moz-image-region: rect(0, 32px, 16px, 16px);
|
||||
}
|
||||
|
||||
menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
%ifdef WINDOWS_AERO
|
||||
/* Win8 and beyond. */
|
||||
@media not all and (-moz-os-version: windows-vista) {
|
||||
|
|
|
@ -198,6 +198,7 @@
|
|||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "mozilla/dom/NodeFilterBinding.h"
|
||||
#include "mozilla/dom/OwningNonNull.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
#include "mozilla/dom/UndoManager.h"
|
||||
#include "mozilla/dom/WebComponentsBinding.h"
|
||||
#include "nsFrame.h"
|
||||
|
@ -10543,8 +10544,9 @@ nsIDocument::ExitFullscreen(nsIDocument* aDoc, bool aRunAsync)
|
|||
}
|
||||
|
||||
// Returns true if the document is a direct child of a cross process parent
|
||||
// mozbrowser iframe. This is the case when the document has a null parent,
|
||||
// and its DocShell reports that it is a browser frame.
|
||||
// mozbrowser iframe or TabParent. This is the case when the document has
|
||||
// a null parent and its DocShell reports that it is a browser frame, or
|
||||
// we can get a TabChild from it.
|
||||
static bool
|
||||
HasCrossProcessParent(nsIDocument* aDocument)
|
||||
{
|
||||
|
@ -10562,7 +10564,12 @@ HasCrossProcessParent(nsIDocument* aDocument)
|
|||
if (!docShell) {
|
||||
return false;
|
||||
}
|
||||
return docShell->GetIsBrowserOrApp();
|
||||
TabChild* tabChild(TabChild::GetFrom(docShell));
|
||||
if (!tabChild) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
|
@ -61,6 +61,7 @@ public class AppConstants {
|
|||
* If our MAX_SDK_VERSION is lower than ICS, we must not be an ICS device.
|
||||
* Otherwise, we need a range check.
|
||||
*/
|
||||
public static final boolean preJBMR2 = MAX_SDK_VERSION < 18 || (MIN_SDK_VERSION < 18 && Build.VERSION.SDK_INT < 18);
|
||||
public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
|
||||
public static final boolean preICS = MAX_SDK_VERSION < 14 || (MIN_SDK_VERSION < 14 && Build.VERSION.SDK_INT < 14);
|
||||
public static final boolean preHCMR2 = MAX_SDK_VERSION < 13 || (MIN_SDK_VERSION < 13 && Build.VERSION.SDK_INT < 13);
|
||||
|
|
|
@ -2526,7 +2526,8 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
// Disable save as PDF for about:home and xul pages.
|
||||
saveAsPDF.setEnabled(!(isAboutHome(tab) ||
|
||||
tab.getContentType().equals("application/vnd.mozilla.xul+xml")));
|
||||
tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||
|
||||
tab.getContentType().startsWith("video/")));
|
||||
|
||||
// Disable find in page for about:home, since it won't work on Java content.
|
||||
findInPage.setEnabled(!isAboutHome(tab));
|
||||
|
|
|
@ -27,11 +27,15 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
||||
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
|
||||
|
@ -92,12 +96,14 @@ import android.media.MediaScannerConnection.MediaScannerConnectionClient;
|
|||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.MessageQueue;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
@ -153,9 +159,6 @@ public class GeckoAppShell
|
|||
* sVibrationMaybePlaying is true. */
|
||||
private static long sVibrationEndTime;
|
||||
|
||||
/* Default value of how fast we should hint the Android sensors. */
|
||||
private static int sDefaultSensorHint = 100;
|
||||
|
||||
private static Sensor gAccelerometerSensor;
|
||||
private static Sensor gLinearAccelerometerSensor;
|
||||
private static Sensor gGyroscopeSensor;
|
||||
|
@ -680,14 +683,14 @@ public class GeckoAppShell
|
|||
if(gOrientationSensor == null)
|
||||
gOrientationSensor = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
|
||||
if (gOrientationSensor != null)
|
||||
sm.registerListener(gi.getSensorEventListener(), gOrientationSensor, sDefaultSensorHint);
|
||||
sm.registerListener(gi.getSensorEventListener(), gOrientationSensor, SensorManager.SENSOR_DELAY_GAME);
|
||||
break;
|
||||
|
||||
case GeckoHalDefines.SENSOR_ACCELERATION:
|
||||
if(gAccelerometerSensor == null)
|
||||
gAccelerometerSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
if (gAccelerometerSensor != null)
|
||||
sm.registerListener(gi.getSensorEventListener(), gAccelerometerSensor, sDefaultSensorHint);
|
||||
sm.registerListener(gi.getSensorEventListener(), gAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
|
||||
break;
|
||||
|
||||
case GeckoHalDefines.SENSOR_PROXIMITY:
|
||||
|
@ -708,14 +711,14 @@ public class GeckoAppShell
|
|||
if(gLinearAccelerometerSensor == null)
|
||||
gLinearAccelerometerSensor = sm.getDefaultSensor(10 /* API Level 9 - TYPE_LINEAR_ACCELERATION */);
|
||||
if (gLinearAccelerometerSensor != null)
|
||||
sm.registerListener(gi.getSensorEventListener(), gLinearAccelerometerSensor, sDefaultSensorHint);
|
||||
sm.registerListener(gi.getSensorEventListener(), gLinearAccelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
|
||||
break;
|
||||
|
||||
case GeckoHalDefines.SENSOR_GYROSCOPE:
|
||||
if(gGyroscopeSensor == null)
|
||||
gGyroscopeSensor = sm.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
|
||||
if (gGyroscopeSensor != null)
|
||||
sm.registerListener(gi.getSensorEventListener(), gGyroscopeSensor, sDefaultSensorHint);
|
||||
sm.registerListener(gi.getSensorEventListener(), gGyroscopeSensor, SensorManager.SENSOR_DELAY_GAME);
|
||||
break;
|
||||
default:
|
||||
Log.w(LOGTAG, "Error! Can't enable unknown SENSOR type " + aSensortype);
|
||||
|
@ -2528,6 +2531,39 @@ public class GeckoAppShell
|
|||
return "DIRECT";
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static boolean isUserRestricted() {
|
||||
if (Versions.preJBMR2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UserManager mgr = (UserManager)getContext().getSystemService(Context.USER_SERVICE);
|
||||
Bundle restrictions = mgr.getUserRestrictions();
|
||||
|
||||
return !restrictions.isEmpty();
|
||||
}
|
||||
|
||||
@WrapElementForJNI
|
||||
public static String getUserRestrictions() {
|
||||
if (Versions.preJBMR2) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
JSONObject json = new JSONObject();
|
||||
UserManager mgr = (UserManager)getContext().getSystemService(Context.USER_SERVICE);
|
||||
Bundle restrictions = mgr.getUserRestrictions();
|
||||
|
||||
Set<String> keys = restrictions.keySet();
|
||||
for (String key : keys) {
|
||||
try {
|
||||
json.put(key, restrictions.get(key));
|
||||
} catch (JSONException e) {
|
||||
}
|
||||
}
|
||||
|
||||
return json.toString();
|
||||
}
|
||||
|
||||
/* Downloads the uri pointed to by a share intent, and alters the intent to point to the locally stored file.
|
||||
*/
|
||||
public static void downloadImageForIntent(final Intent intent) {
|
||||
|
|
|
@ -746,8 +746,11 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
|
|||
}
|
||||
|
||||
@WrapElementForJNI(allowMultithread = true)
|
||||
public void deactivateProgram() {
|
||||
public void deactivateProgramAndRestoreState(boolean enableScissor,
|
||||
int scissorX, int scissorY, int scissorW, int scissorH)
|
||||
{
|
||||
mLayerRenderer.deactivateDefaultProgram();
|
||||
mLayerRenderer.restoreState(enableScissor, scissorX, scissorY, scissorW, scissorH);
|
||||
}
|
||||
|
||||
private void geometryChanged(DisplayPortMetrics displayPort) {
|
||||
|
|
|
@ -249,6 +249,15 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
|
|||
GLES20.glUseProgram(0);
|
||||
}
|
||||
|
||||
void restoreState(boolean enableScissor, int scissorX, int scissorY, int scissorW, int scissorH) {
|
||||
GLES20.glScissor(scissorX, scissorY, scissorW, scissorH);
|
||||
if (enableScissor) {
|
||||
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
|
||||
} else {
|
||||
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
public int getMaxTextureSize() {
|
||||
return mMaxTextureSize;
|
||||
}
|
||||
|
@ -558,7 +567,7 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
|
|||
@JNITarget
|
||||
public void drawBackground() {
|
||||
// Any GL state which is changed here must be restored in
|
||||
// CompositorOGL::RestoreState
|
||||
// restoreState(...)
|
||||
|
||||
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
|
||||
|
||||
|
@ -577,7 +586,7 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
|
|||
@JNITarget
|
||||
public void drawForeground() {
|
||||
// Any GL state which is changed here must be restored in
|
||||
// CompositorOGL::RestoreState
|
||||
// restoreState(...)
|
||||
|
||||
/* Draw any extra layers that were added (likely plugins) */
|
||||
if (mExtraLayers.size() > 0) {
|
||||
|
|
Двоичные данные
mobile/android/base/resources/drawable-hdpi/arrow_popup_bg.9.png
До Ширина: | Высота: | Размер: 279 B После Ширина: | Высота: | Размер: 311 B |
До Ширина: | Высота: | Размер: 866 B После Ширина: | Высота: | Размер: 921 B |
До Ширина: | Высота: | Размер: 594 B После Ширина: | Высота: | Размер: 633 B |
До Ширина: | Высота: | Размер: 1.0 KiB После Ширина: | Высота: | Размер: 1.1 KiB |
Двоичные данные
mobile/android/base/resources/drawable-mdpi/arrow_popup_bg.9.png
До Ширина: | Высота: | Размер: 221 B После Ширина: | Высота: | Размер: 254 B |
До Ширина: | Высота: | Размер: 325 B После Ширина: | Высота: | Размер: 355 B |
|
@ -6,6 +6,7 @@ package org.mozilla.gecko.tests.components;
|
|||
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertEquals;
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertFalse;
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull;
|
||||
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -28,6 +29,8 @@ import com.jayway.android.robotium.solo.Solo;
|
|||
* A class representing any interactions that take place on the app menu.
|
||||
*/
|
||||
public class AppMenuComponent extends BaseComponent {
|
||||
private static final long MAX_WAITTIME_FOR_MENU_UPDATE_IN_MS = 1000L;
|
||||
|
||||
private Boolean hasLegacyMenu = null;
|
||||
|
||||
public enum MenuItem {
|
||||
|
@ -112,6 +115,7 @@ public class AppMenuComponent extends BaseComponent {
|
|||
pressMenuItem(MenuItem.PAGE.getString(mSolo));
|
||||
|
||||
final View pageMenuItemView = findAppMenuItemView(pageMenuItem.getString(mSolo));
|
||||
fAssertNotNull("The page menu item is not null", pageMenuItemView);
|
||||
fAssertFalse("The page menu item is not enabled", pageMenuItemView.isEnabled());
|
||||
fAssertEquals("The page menu item is visible", View.VISIBLE, pageMenuItemView.getVisibility());
|
||||
} else {
|
||||
|
@ -141,6 +145,8 @@ public class AppMenuComponent extends BaseComponent {
|
|||
* This method is dependent on not having two views with equivalent contentDescription / text.
|
||||
*/
|
||||
private View findAppMenuItemView(String text) {
|
||||
mSolo.waitForText(text, 1, MAX_WAITTIME_FOR_MENU_UPDATE_IN_MS);
|
||||
|
||||
final List<View> views = mSolo.getViews();
|
||||
|
||||
final List<MenuItemActionBar> menuItemActionBarList = RobotiumUtils.filterViews(MenuItemActionBar.class, views);
|
||||
|
|
|
@ -105,6 +105,7 @@ skip-if = android_version == "10"
|
|||
[testNativeWindow]
|
||||
[testOrderedBroadcast]
|
||||
[testResourceSubstitutions]
|
||||
[testRestrictedProfiles]
|
||||
[testSharedPreferences]
|
||||
[testSimpleDiscovery]
|
||||
[testUITelemetry]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.mozilla.gecko.tests;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.tests.components.AppMenuComponent;
|
||||
import org.mozilla.gecko.tests.helpers.GeckoHelper;
|
||||
import org.mozilla.gecko.tests.helpers.NavigationHelper;
|
||||
|
@ -23,12 +25,34 @@ public class testAppMenuPathways extends UITest {
|
|||
// Page menu should be disabled in about:home.
|
||||
mAppMenu.assertMenuItemIsDisabledAndVisible(AppMenuComponent.PageMenuItem.SAVE_AS_PDF);
|
||||
|
||||
// Navigate to a page to test save as pdf functionality.
|
||||
// Generate a mock Content:LocationChange message with video mime-type for the current tab (tabId = 0).
|
||||
final JSONObject message = new JSONObject();
|
||||
try {
|
||||
message.put("contentType", "video/webm");
|
||||
message.put("baseDomain", "webmfiles.org");
|
||||
message.put("type", "Content:LocationChange");
|
||||
message.put("sameDocument", false);
|
||||
message.put("userSearch", "");
|
||||
message.put("uri", getAbsoluteIpUrl("/big-buck-bunny_trailer.webm"));
|
||||
message.put("tabID", 0);
|
||||
} catch (Exception ex) {
|
||||
mAsserter.ok(false, "exception in testSaveAsPDFPathway", ex.toString());
|
||||
}
|
||||
|
||||
// Mock video playback with the generated message and Content:LocationChange event.
|
||||
Tabs.getInstance().handleMessage("Content:LocationChange", message);
|
||||
|
||||
// Save as pdf menu is disabled while playing video.
|
||||
mAppMenu.assertMenuItemIsDisabledAndVisible(AppMenuComponent.PageMenuItem.SAVE_AS_PDF);
|
||||
|
||||
// The above mock video playback test changes Java state, but not the associated JS state.
|
||||
// Navigate to a new page so that the Java state is cleared.
|
||||
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
|
||||
mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
|
||||
|
||||
// Test save as pdf functionality.
|
||||
// The following call doesn't wait for the resulting pdf but checks that no exception are thrown.
|
||||
// NOTE: save as pdf functionality must be done at the end as it is slow and cause other test operations to fail.
|
||||
mAppMenu.pressMenuItem(AppMenuComponent.PageMenuItem.SAVE_AS_PDF);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.mozilla.gecko.tests;
|
||||
|
||||
|
||||
|
||||
public class testRestrictedProfiles extends JavascriptTest {
|
||||
public testRestrictedProfiles() {
|
||||
super("testRestrictedProfiles.js");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// -*- 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/. */
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/ctypes.jsm");
|
||||
Cu.import("resource://gre/modules/JNI.jsm");
|
||||
|
||||
add_task(function test_isUserRestricted() {
|
||||
// Make sure the parental controls service is available
|
||||
do_check_true("@mozilla.org/parental-controls-service;1" in Cc);
|
||||
|
||||
let pc = Cc["@mozilla.org/parental-controls-service;1"].createInstance(Ci.nsIParentalControlsService);
|
||||
|
||||
// In an admin profile, like the tests: enabled = false
|
||||
// In a restricted profile: enabled = true
|
||||
do_check_false(pc.parentalControlsEnabled);
|
||||
|
||||
//run_next_test();
|
||||
});
|
||||
/*
|
||||
// NOTE: JNI.jsm has no way to call a string method yet
|
||||
add_task(function test_getUserRestrictions() {
|
||||
// In an admin profile, like the tests: {}
|
||||
// In a restricted profile: {"no_modify_accounts":true,"no_share_location":true}
|
||||
let restrictions = "{}";
|
||||
|
||||
let jni = null;
|
||||
try {
|
||||
jni = new JNI();
|
||||
let cls = jni.findClass("org/mozilla/gecko/GeckoAppShell");
|
||||
let method = jni.getStaticMethodID(cls, "getUserRestrictions", "()Ljava/lang/String;");
|
||||
restrictions = jni.callStaticStringMethod(cls, method);
|
||||
} finally {
|
||||
if (jni != null) {
|
||||
jni.close();
|
||||
}
|
||||
}
|
||||
|
||||
do_check_eq(restrictions, "{}");
|
||||
});
|
||||
*/
|
||||
run_next_test();
|
|
@ -8,6 +8,7 @@
|
|||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm");
|
||||
|
||||
function ok(passed, text) {
|
||||
do_report_result(passed, text, Components.stack.caller, false);
|
||||
|
@ -26,14 +27,38 @@ function middle(element) {
|
|||
return [x, y];
|
||||
}
|
||||
|
||||
// We must register a target and make a "mock" service for the target
|
||||
var testTarget = {
|
||||
target: "test:service",
|
||||
factory: function(service) { /* dummy */ },
|
||||
types: ["video/mp4", "video/webm"],
|
||||
extensions: ["mp4", "webm"]
|
||||
};
|
||||
|
||||
add_test(function setup_browser() {
|
||||
chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let BrowserApp = chromeWin.BrowserApp;
|
||||
|
||||
do_register_cleanup(function cleanup() {
|
||||
BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
|
||||
SimpleServiceDiscovery.unregisterTarget(testTarget);
|
||||
});
|
||||
|
||||
// We need to register a target or processService will ignore us
|
||||
SimpleServiceDiscovery.registerTarget(testTarget);
|
||||
|
||||
// Create a pretend service
|
||||
let service = {
|
||||
location: "http://mochi.test:8888/tests/robocop/simpleservice.xml",
|
||||
target: "test:service"
|
||||
};
|
||||
|
||||
do_print("Force a detailed ping from a pretend service");
|
||||
|
||||
// Poke the service directly to get the discovery to happen
|
||||
SimpleServiceDiscovery._processService(service);
|
||||
|
||||
// Load our test web page with <video> elements
|
||||
let url = "http://mochi.test:8888/tests/robocop/video_discovery.html";
|
||||
browser = BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id }).browser;
|
||||
browser.addEventListener("load", function startTests(event) {
|
||||
|
|
|
@ -458,6 +458,10 @@ var CastingApps = {
|
|||
}
|
||||
});
|
||||
|
||||
if (items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prompt = new Prompt({
|
||||
title: Strings.browser.GetStringFromName("casting.prompt")
|
||||
}).setSingleChoiceItems(items).show(function(data) {
|
||||
|
|
|
@ -246,8 +246,8 @@ var SimpleServiceDiscovery = {
|
|||
|
||||
getSupportedExtensions: function() {
|
||||
let extensions = [];
|
||||
this._targets.forEach(function(target) {
|
||||
extensions = extensions.concat(target.extensions);
|
||||
this.services.forEach(function(service) {
|
||||
extensions = extensions.concat(service.extensions);
|
||||
}, this);
|
||||
return extensions.filter(function(extension, pos) {
|
||||
return extensions.indexOf(extension) == pos;
|
||||
|
@ -256,8 +256,8 @@ var SimpleServiceDiscovery = {
|
|||
|
||||
getSupportedMimeTypes: function() {
|
||||
let types = [];
|
||||
this._targets.forEach(function(target) {
|
||||
types = types.concat(target.types);
|
||||
this.services.forEach(function(service) {
|
||||
types = types.concat(service.types);
|
||||
}, this);
|
||||
return types.filter(function(type, pos) {
|
||||
return types.indexOf(type) == pos;
|
||||
|
|
|
@ -19,6 +19,10 @@ if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
|
|||
UNIFIED_SOURCES += [
|
||||
'nsParentalControlsServiceCocoa.mm',
|
||||
]
|
||||
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
|
||||
UNIFIED_SOURCES += [
|
||||
'nsParentalControlsServiceAndroid.cpp',
|
||||
]
|
||||
else:
|
||||
SOURCES += [
|
||||
'nsParentalControlsServiceDefault.cpp',
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsParentalControlsService.h"
|
||||
#include "AndroidBridge.h"
|
||||
#include "nsString.h"
|
||||
#include "nsIFile.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsParentalControlsService, nsIParentalControlsService)
|
||||
|
||||
nsParentalControlsService::nsParentalControlsService() :
|
||||
mEnabled(false)
|
||||
{
|
||||
if (mozilla::AndroidBridge::HasEnv()) {
|
||||
mEnabled = mozilla::widget::android::GeckoAppShell::IsUserRestricted();
|
||||
}
|
||||
}
|
||||
|
||||
nsParentalControlsService::~nsParentalControlsService()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsParentalControlsService::GetParentalControlsEnabled(bool *aResult)
|
||||
{
|
||||
*aResult = mEnabled;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsParentalControlsService::GetBlockFileDownloadsEnabled(bool *aResult)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsParentalControlsService::GetLoggingEnabled(bool *aResult)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsParentalControlsService::Log(int16_t aEntryType,
|
||||
bool aBlocked,
|
||||
nsIURI *aSource,
|
||||
nsIFile *aTarget)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsParentalControlsService::RequestURIOverride(nsIURI *aTarget,
|
||||
nsIInterfaceRequestor *aWindowContext,
|
||||
bool *_retval)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsParentalControlsService::RequestURIOverrides(nsIArray *aTargets,
|
||||
nsIInterfaceRequestor *aWindowContext,
|
||||
bool *_retval)
|
||||
{
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
|
@ -375,6 +375,48 @@ addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => {
|
|||
loadGroup.adjustPriority(msg.data.adjustment);
|
||||
});
|
||||
|
||||
let DOMFullscreenManager = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "ask-parent-to-exit-fullscreen", false);
|
||||
Services.obs.addObserver(this, "ask-parent-to-rollback-fullscreen", false);
|
||||
addMessageListener("DOMFullscreen:ChildrenMustExit", () => {
|
||||
let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
utils.exitFullscreen();
|
||||
});
|
||||
addEventListener("unload", () => {
|
||||
Services.obs.removeObserver(this, "ask-parent-to-exit-fullscreen");
|
||||
Services.obs.removeObserver(this, "ask-parent-to-rollback-fullscreen");
|
||||
});
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
// Observer notifications are global, which means that these notifications
|
||||
// might be coming from elements that are not actually children within this
|
||||
// windows' content. We should ignore those. This will not be necessary once
|
||||
// we fix bug 1053413 and stop using observer notifications for this stuff.
|
||||
if (aSubject.defaultView.top !== content) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aTopic) {
|
||||
case "ask-parent-to-exit-fullscreen": {
|
||||
sendAsyncMessage("DOMFullscreen:RequestExit");
|
||||
break;
|
||||
}
|
||||
case "ask-parent-to-rollback-fullscreen": {
|
||||
sendAsyncMessage("DOMFullscreen:RequestRollback");
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
DOMFullscreenManager.init();
|
||||
|
||||
let AutoCompletePopup = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),
|
||||
|
||||
|
|
|
@ -209,6 +209,8 @@
|
|||
this.messageManager.addMessageListener("DocumentInserted", this);
|
||||
this.messageManager.addMessageListener("FullZoomChange", this);
|
||||
this.messageManager.addMessageListener("TextZoomChange", this);
|
||||
this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
|
||||
this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
|
||||
this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
|
||||
|
||||
if (this.hasAttribute("selectpopup")) {
|
||||
|
@ -221,9 +223,17 @@
|
|||
let RemoteController = Components.utils.import(jsm, {}).RemoteController;
|
||||
this._controller = new RemoteController(this);
|
||||
this.controllers.appendController(this._controller);
|
||||
|
||||
Services.obs.addObserver(this, "ask-children-to-exit-fullscreen", false);
|
||||
]]>
|
||||
</constructor>
|
||||
|
||||
<destructor>
|
||||
<![CDATA[
|
||||
Services.obs.removeObserver(this, "ask-children-to-exit-fullscreen");
|
||||
]]>
|
||||
</destructor>
|
||||
|
||||
<method name="receiveMessage">
|
||||
<parameter name="aMessage"/>
|
||||
<body><![CDATA[
|
||||
|
@ -276,6 +286,20 @@
|
|||
break;
|
||||
}
|
||||
|
||||
case "DOMFullscreen:RequestExit": {
|
||||
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.exitFullscreen();
|
||||
break;
|
||||
}
|
||||
|
||||
case "DOMFullscreen:RequestRollback": {
|
||||
let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.remoteFrameFullscreenReverted();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Delegate to browser.xml.
|
||||
return this._receiveMessage(aMessage);
|
||||
|
@ -284,6 +308,19 @@
|
|||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="observe">
|
||||
<parameter name="aSubject"/>
|
||||
<parameter name="aTopic"/>
|
||||
<parameter name="aData"/>
|
||||
<body><![CDATA[
|
||||
if (aTopic == "ask-children-to-exit-fullscreen") {
|
||||
if (aSubject == window.document) {
|
||||
this.messageManager.sendAsyncMessage("DOMFullscreen:ChildrenMustExit");
|
||||
}
|
||||
}
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
For out-of-process code, event.screen[XY] is relative to the
|
||||
left/top of the content view. For in-process code,
|
||||
|
|
|
@ -7,13 +7,31 @@
|
|||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const gcli = require("gcli/index");
|
||||
const { DebuggerServer } = require("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DevToolsLoader",
|
||||
"resource://gre/modules/devtools/Loader.jsm");
|
||||
|
||||
const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Ci.nsIStringBundleService)
|
||||
.createBundle("chrome://branding/locale/brand.properties")
|
||||
.GetStringFromName("brandShortName");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "debuggerServer", () => {
|
||||
// Create a separate loader instance, so that we can be sure to receive
|
||||
// a separate instance of the DebuggingServer from the rest of the
|
||||
// devtools. This allows us to safely use the tools against even the
|
||||
// actors and DebuggingServer itself, especially since we can mark
|
||||
// serverLoader as invisible to the debugger (unlike the usual loader
|
||||
// settings).
|
||||
let serverLoader = new DevToolsLoader();
|
||||
serverLoader.invisibleToDebugger = true;
|
||||
serverLoader.main("devtools/server/main");
|
||||
let debuggerServer = serverLoader.DebuggerServer;
|
||||
debuggerServer.init();
|
||||
debuggerServer.addBrowserActors();
|
||||
return debuggerServer;
|
||||
});
|
||||
|
||||
exports.items = [
|
||||
{
|
||||
name: "listen",
|
||||
|
@ -30,16 +48,12 @@ exports.items = [
|
|||
}
|
||||
],
|
||||
exec: function(args, context) {
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
var reply = DebuggerServer.openListener(args.port);
|
||||
var reply = debuggerServer.openListener(args.port);
|
||||
if (!reply) {
|
||||
throw new Error(gcli.lookup("listenDisabledOutput"));
|
||||
}
|
||||
|
||||
if (DebuggerServer.initialized) {
|
||||
if (debuggerServer.initialized) {
|
||||
return gcli.lookupFormat("listenInitOutput", [ "" + args.port ]);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
const Services = require("Services");
|
||||
|
||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const events = require("sdk/event/core");
|
||||
const { on: systemOn, off: systemOff } = require("sdk/system/events");
|
||||
const { setTimeout, clearTimeout } = require("sdk/timers");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { CallWatcherActor, CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
||||
const { ThreadActor } = require("devtools/server/actors/script");
|
||||
|
||||
const { on, once, off, emit } = events;
|
||||
const { method, Arg, Option, RetVal } = protocol;
|
||||
|
||||
|
@ -27,6 +25,10 @@ exports.unregister = function(handle) {
|
|||
handle.removeGlobalActor(WebAudioActor);
|
||||
};
|
||||
|
||||
// In milliseconds, how often should AudioNodes poll to see
|
||||
// if an AudioParam's value has changed to emit to the client.
|
||||
const PARAM_POLLING_FREQUENCY = 1000;
|
||||
|
||||
const AUDIO_GLOBALS = [
|
||||
"AudioContext", "AudioNode"
|
||||
];
|
||||
|
@ -146,6 +148,10 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
|||
}
|
||||
},
|
||||
|
||||
destroy: function(conn) {
|
||||
protocol.Actor.prototype.destroy.call(this, conn);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the name of the audio type.
|
||||
* Examples: "OscillatorNode", "MediaElementAudioSourceNode"
|
||||
|
@ -187,6 +193,7 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
|||
node[param].value = value;
|
||||
else
|
||||
node[param] = value;
|
||||
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
return constructError(e);
|
||||
|
@ -221,14 +228,7 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
|||
// AudioBuffer or Float32Array references and the like,
|
||||
// so this just formats the value to be displayed in the VariablesView,
|
||||
// without using real grips and managing via actor pools.
|
||||
let grip;
|
||||
try {
|
||||
grip = ThreadActor.prototype.createValueGrip(value);
|
||||
}
|
||||
catch (e) {
|
||||
grip = createObjectGrip(value);
|
||||
}
|
||||
return grip;
|
||||
return createGrip(value);
|
||||
}, {
|
||||
request: {
|
||||
param: Arg(0, "string")
|
||||
|
@ -252,16 +252,27 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
|
|||
}),
|
||||
|
||||
/**
|
||||
* Get an array of objects each containing a `param` and `value` property,
|
||||
* corresponding to a property name and current value of the audio node.
|
||||
* Get an array of objects each containing a `param`, `value` and `flags` property,
|
||||
* corresponding to a property name and current value of the audio node, and any
|
||||
* associated flags as defined by NODE_PROPERTIES.
|
||||
*/
|
||||
getParams: method(function (param) {
|
||||
getParams: method(function () {
|
||||
let props = Object.keys(NODE_PROPERTIES[this.type]);
|
||||
return props.map(prop =>
|
||||
({ param: prop, value: this.getParam(prop), flags: this.getParamFlags(prop) }));
|
||||
}, {
|
||||
response: { params: RetVal("json") }
|
||||
})
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not
|
||||
* the underlying AudioNode has been collected yet or not.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
isAlive: function () {
|
||||
return !!this.node.get();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -407,6 +418,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
this.tabActor = null;
|
||||
this._initialized = false;
|
||||
off(this._callWatcher._contentObserver, "global-destroyed", this._onGlobalDestroyed);
|
||||
this.disableChangeParamEvents();
|
||||
this._nativeToActorID = null;
|
||||
this._callWatcher.eraseRecording();
|
||||
this._callWatcher.finalize();
|
||||
|
@ -415,6 +427,59 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
oneway: true
|
||||
}),
|
||||
|
||||
/**
|
||||
* Takes an AudioNodeActor and a duration specifying how often
|
||||
* should the node's parameters be polled to detect changes. Emits
|
||||
* `change-param` when a change is found.
|
||||
*
|
||||
* Currently, only one AudioNodeActor can be listened to at a time.
|
||||
*
|
||||
* `wait` is used in tests to specify the poll timer.
|
||||
*/
|
||||
enableChangeParamEvents: method(function (nodeActor, wait) {
|
||||
// For now, only have one node being polled
|
||||
this.disableChangeParamEvents();
|
||||
|
||||
// Ignore if node is dead
|
||||
if (!nodeActor.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previous = mapAudioParams(nodeActor);
|
||||
|
||||
// Store the ID of the node being polled
|
||||
this._pollingID = nodeActor.actorID;
|
||||
|
||||
this.poller = new Poller(() => {
|
||||
// If node has been collected, disable param polling
|
||||
if (!nodeActor.isAlive()) {
|
||||
this.disableChangeParamEvents();
|
||||
return;
|
||||
}
|
||||
|
||||
let current = mapAudioParams(nodeActor);
|
||||
diffAudioParams(previous, current).forEach(changed => {
|
||||
this._onChangeParam(nodeActor, changed);
|
||||
});
|
||||
previous = current;
|
||||
}).on(wait || PARAM_POLLING_FREQUENCY);
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "audionode"),
|
||||
wait: Arg(1, "nullable:number"),
|
||||
},
|
||||
oneway: true
|
||||
}),
|
||||
|
||||
disableChangeParamEvents: method(function () {
|
||||
if (this.poller) {
|
||||
this.poller.off();
|
||||
}
|
||||
this._pollingID = null;
|
||||
}, {
|
||||
oneway: true
|
||||
}),
|
||||
|
||||
/**
|
||||
* Events emitted by this actor.
|
||||
*/
|
||||
|
@ -437,12 +502,6 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
dest: Option(0, "audionode"),
|
||||
param: Option(0, "string")
|
||||
},
|
||||
"change-param": {
|
||||
type: "changeParam",
|
||||
source: Option(0, "audionode"),
|
||||
param: Option(0, "string"),
|
||||
value: Option(0, "string")
|
||||
},
|
||||
"create-node": {
|
||||
type: "createNode",
|
||||
source: Arg(0, "audionode")
|
||||
|
@ -450,6 +509,13 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
"destroy-node": {
|
||||
type: "destroyNode",
|
||||
source: Arg(0, "audionode")
|
||||
},
|
||||
"change-param": {
|
||||
type: "changeParam",
|
||||
param: Option(0, "string"),
|
||||
newValue: Option(0, "json"),
|
||||
oldValue: Option(0, "json"),
|
||||
actorID: Option(0, "string")
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -473,7 +539,7 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
/**
|
||||
* Takes an XrayWrapper node, and attaches the node's `nativeID`
|
||||
* to the AudioParams as `_parentID`, as well as the the type of param
|
||||
* as a string on `_paramName`.
|
||||
* as a string on `_paramName`. Used to tag AudioParams for `connect-param` events.
|
||||
*/
|
||||
_instrumentParams: function (node) {
|
||||
let type = getConstructorName(node);
|
||||
|
@ -493,6 +559,14 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
* created), so make a new actor and store that.
|
||||
*/
|
||||
_getActorByNativeID: function (nativeID) {
|
||||
// If the WebAudioActor has already been finalized, the `_nativeToActorID`
|
||||
// map will already be destroyed -- the lingering destruction events
|
||||
// seem to only occur in e10s, so add an extra check here to disregard
|
||||
// these late events
|
||||
if (!this._nativeToActorID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure we have a Number, rather than a string
|
||||
// return via notification.
|
||||
nativeID = ~~nativeID;
|
||||
|
@ -544,15 +618,12 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
},
|
||||
|
||||
/**
|
||||
* Called when a parameter changes on an audio node
|
||||
* Called when an AudioParam that's being listened to changes.
|
||||
* Takes an AudioNodeActor and an object with `newValue`, `oldValue`, and `param` name.
|
||||
*/
|
||||
_onParamChange: function (node, param, value) {
|
||||
let actor = this._getActorByNativeID(node.id);
|
||||
emit(this, "param-change", {
|
||||
source: actor,
|
||||
param: param,
|
||||
value: value
|
||||
});
|
||||
_onChangeParam: function (actor, changed) {
|
||||
changed.actorID = actor.actorID;
|
||||
emit(this, "change-param", changed);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -563,7 +634,8 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
emit(this, "create-node", actor);
|
||||
},
|
||||
|
||||
/** Called when `webaudio-node-demise` is triggered,
|
||||
/**
|
||||
* Called when `webaudio-node-demise` is triggered,
|
||||
* and emits the associated actor to the front if found.
|
||||
*/
|
||||
_onDestroyNode: function ({data}) {
|
||||
|
@ -576,6 +648,10 @@ let WebAudioActor = exports.WebAudioActor = protocol.ActorClass({
|
|||
// notifications for a document that no longer exists,
|
||||
// the mapping should not be found, so we do not emit an event.
|
||||
if (actor) {
|
||||
// Turn off polling for changes if on for this node
|
||||
if (this._pollingID === actor.actorID) {
|
||||
this.disableChangeParamEvents();
|
||||
}
|
||||
this._nativeToActorID.delete(nativeID);
|
||||
emit(this, "destroy-node", actor);
|
||||
}
|
||||
|
@ -663,17 +739,109 @@ function getConstructorName (obj) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a grip-like object to pass in renderable information
|
||||
* to the front-end for things like Float32Arrays, AudioBuffers,
|
||||
* without tracking them in an actor pool.
|
||||
* Create a value grip for `value`, or fallback to a grip-like object
|
||||
* for renderable information for the front-end for things like Float32Arrays,
|
||||
* AudioBuffers, without tracking them in an actor pool.
|
||||
*/
|
||||
function createObjectGrip (value) {
|
||||
return {
|
||||
type: "object",
|
||||
preview: {
|
||||
kind: "ObjectWithText",
|
||||
text: ""
|
||||
},
|
||||
class: getConstructorName(value)
|
||||
};
|
||||
function createGrip (value) {
|
||||
try {
|
||||
return ThreadActor.prototype.createValueGrip(value);
|
||||
}
|
||||
catch (e) {
|
||||
return {
|
||||
type: "object",
|
||||
preview: {
|
||||
kind: "ObjectWithText",
|
||||
text: ""
|
||||
},
|
||||
class: getConstructorName(value)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an AudioNodeActor and maps its current parameter values
|
||||
* to a hash, where the property is the AudioParam name, and value
|
||||
* is the current value.
|
||||
*/
|
||||
function mapAudioParams (node) {
|
||||
return node.getParams().reduce(function (obj, p) {
|
||||
obj[p.param] = p.value;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an object of previous and current values of audio parameters,
|
||||
* and compares them. If they differ, emit a `change-param` event.
|
||||
*
|
||||
* @param Object prev
|
||||
* Hash of previous set of AudioParam values.
|
||||
* @param Object current
|
||||
* Hash of current set of AudioParam values.
|
||||
*/
|
||||
function diffAudioParams (prev, current) {
|
||||
return Object.keys(current).reduce((changed, param) => {
|
||||
if (!equalGrips(current[param], prev[param])) {
|
||||
changed.push({
|
||||
param: param,
|
||||
oldValue: prev[param],
|
||||
newValue: current[param]
|
||||
});
|
||||
}
|
||||
return changed;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two grip objects to determine if they're equal or not.
|
||||
*
|
||||
* @param Any a
|
||||
* @param Any a
|
||||
* @return Boolean
|
||||
*/
|
||||
function equalGrips (a, b) {
|
||||
let aType = typeof a;
|
||||
let bType = typeof b;
|
||||
if (aType !== bType) {
|
||||
return false;
|
||||
} else if (aType === "object") {
|
||||
// In this case, we are comparing two objects, like an ArrayBuffer or Float32Array,
|
||||
// or even just plain "null"s (which grip's will have `type` property "null",
|
||||
// and we have no way of showing more information than its class, so assume
|
||||
// these are equal since nothing can be updated with information of value.
|
||||
if (a.type === b.type) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise return false -- this could be a case of a property going from `null`
|
||||
// to having an ArrayBuffer or an object, in which case we should update it.
|
||||
return false;
|
||||
} else {
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Poller class -- takes a function, and call be turned on and off
|
||||
* via methods to execute `fn` on the interval specified during `on`.
|
||||
*/
|
||||
function Poller (fn) {
|
||||
this.fn = fn;
|
||||
}
|
||||
|
||||
Poller.prototype.on = function (wait) {
|
||||
let poller = this;
|
||||
poller.timer = setTimeout(poll, wait);
|
||||
function poll () {
|
||||
poller.fn();
|
||||
poller.timer = setTimeout(poll, wait);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Poller.prototype.off = function () {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
|
|
@ -55,6 +55,7 @@ jmethodID GeckoAppShell::jGetScreenDepthWrapper = 0;
|
|||
jmethodID GeckoAppShell::jGetScreenOrientationWrapper = 0;
|
||||
jmethodID GeckoAppShell::jGetShowPasswordSetting = 0;
|
||||
jmethodID GeckoAppShell::jGetSystemColoursWrapper = 0;
|
||||
jmethodID GeckoAppShell::jGetUserRestrictions = 0;
|
||||
jmethodID GeckoAppShell::jHandleGeckoMessageWrapper = 0;
|
||||
jmethodID GeckoAppShell::jHandleUncaughtException = 0;
|
||||
jmethodID GeckoAppShell::jHideProgressDialog = 0;
|
||||
|
@ -62,6 +63,7 @@ jmethodID GeckoAppShell::jInitCameraWrapper = 0;
|
|||
jmethodID GeckoAppShell::jIsNetworkLinkKnown = 0;
|
||||
jmethodID GeckoAppShell::jIsNetworkLinkUp = 0;
|
||||
jmethodID GeckoAppShell::jIsTablet = 0;
|
||||
jmethodID GeckoAppShell::jIsUserRestricted = 0;
|
||||
jmethodID GeckoAppShell::jKillAnyZombies = 0;
|
||||
jmethodID GeckoAppShell::jLoadPluginClass = 0;
|
||||
jmethodID GeckoAppShell::jLockScreenOrientation = 0;
|
||||
|
@ -142,6 +144,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
|
|||
jGetScreenOrientationWrapper = getStaticMethod("getScreenOrientation", "()S");
|
||||
jGetShowPasswordSetting = getStaticMethod("getShowPasswordSetting", "()Z");
|
||||
jGetSystemColoursWrapper = getStaticMethod("getSystemColors", "()[I");
|
||||
jGetUserRestrictions = getStaticMethod("getUserRestrictions", "()Ljava/lang/String;");
|
||||
jHandleGeckoMessageWrapper = getStaticMethod("handleGeckoMessage", "(Lorg/mozilla/gecko/util/NativeJSContainer;)V");
|
||||
jHandleUncaughtException = getStaticMethod("handleUncaughtException", "(Ljava/lang/Thread;Ljava/lang/Throwable;)V");
|
||||
jHideProgressDialog = getStaticMethod("hideProgressDialog", "()V");
|
||||
|
@ -149,6 +152,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
|
|||
jIsNetworkLinkKnown = getStaticMethod("isNetworkLinkKnown", "()Z");
|
||||
jIsNetworkLinkUp = getStaticMethod("isNetworkLinkUp", "()Z");
|
||||
jIsTablet = getStaticMethod("isTablet", "()Z");
|
||||
jIsUserRestricted = getStaticMethod("isUserRestricted", "()Z");
|
||||
jKillAnyZombies = getStaticMethod("killAnyZombies", "()V");
|
||||
jLoadPluginClass = getStaticMethod("loadPluginClass", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;");
|
||||
jLockScreenOrientation = getStaticMethod("lockScreenOrientation", "(I)V");
|
||||
|
@ -780,6 +784,19 @@ jintArray GeckoAppShell::GetSystemColoursWrapper() {
|
|||
return ret;
|
||||
}
|
||||
|
||||
jstring GeckoAppShell::GetUserRestrictions() {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(1) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_CRASH("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
jobject temp = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetUserRestrictions);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
jstring ret = static_cast<jstring>(env->PopLocalFrame(temp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GeckoAppShell::HandleGeckoMessageWrapper(jobject a0) {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(1) != 0) {
|
||||
|
@ -872,6 +889,19 @@ bool GeckoAppShell::IsTablet() {
|
|||
return temp;
|
||||
}
|
||||
|
||||
bool GeckoAppShell::IsUserRestricted() {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(0) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_CRASH("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
bool temp = env->CallStaticBooleanMethod(mGeckoAppShellClass, jIsUserRestricted);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
env->PopLocalFrame(nullptr);
|
||||
return temp;
|
||||
}
|
||||
|
||||
void GeckoAppShell::KillAnyZombies() {
|
||||
JNIEnv *env = AndroidBridge::GetJNIEnv();
|
||||
if (env->PushLocalFrame(0) != 0) {
|
||||
|
@ -1717,7 +1747,7 @@ jclass GeckoLayerClient::mGeckoLayerClientClass = 0;
|
|||
jmethodID GeckoLayerClient::jActivateProgram = 0;
|
||||
jmethodID GeckoLayerClient::jContentDocumentChanged = 0;
|
||||
jmethodID GeckoLayerClient::jCreateFrame = 0;
|
||||
jmethodID GeckoLayerClient::jDeactivateProgram = 0;
|
||||
jmethodID GeckoLayerClient::jDeactivateProgramAndRestoreState = 0;
|
||||
jmethodID GeckoLayerClient::jGetDisplayPort = 0;
|
||||
jmethodID GeckoLayerClient::jIsContentDocumentDisplayed = 0;
|
||||
jmethodID GeckoLayerClient::jProgressiveUpdateCallback = 0;
|
||||
|
@ -1732,7 +1762,7 @@ void GeckoLayerClient::InitStubs(JNIEnv *jEnv) {
|
|||
jActivateProgram = getMethod("activateProgram", "()V");
|
||||
jContentDocumentChanged = getMethod("contentDocumentChanged", "()V");
|
||||
jCreateFrame = getMethod("createFrame", "()Lorg/mozilla/gecko/gfx/LayerRenderer$Frame;");
|
||||
jDeactivateProgram = getMethod("deactivateProgram", "()V");
|
||||
jDeactivateProgramAndRestoreState = getMethod("deactivateProgramAndRestoreState", "(ZIIII)V");
|
||||
jGetDisplayPort = getMethod("getDisplayPort", "(ZZILorg/mozilla/gecko/gfx/ImmutableViewportMetrics;)Lorg/mozilla/gecko/gfx/DisplayPortMetrics;");
|
||||
jIsContentDocumentDisplayed = getMethod("isContentDocumentDisplayed", "()Z");
|
||||
jProgressiveUpdateCallback = getMethod("progressiveUpdateCallback", "(ZFFFFFZ)Lorg/mozilla/gecko/gfx/ProgressiveUpdateData;");
|
||||
|
@ -1786,14 +1816,21 @@ jobject GeckoLayerClient::CreateFrame() {
|
|||
return ret;
|
||||
}
|
||||
|
||||
void GeckoLayerClient::DeactivateProgram() {
|
||||
void GeckoLayerClient::DeactivateProgramAndRestoreState(bool a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4) {
|
||||
JNIEnv *env = GetJNIForThread();
|
||||
if (env->PushLocalFrame(0) != 0) {
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
MOZ_CRASH("Exception should have caused crash.");
|
||||
}
|
||||
|
||||
env->CallVoidMethod(wrapped_obj, jDeactivateProgram);
|
||||
jvalue args[5];
|
||||
args[0].z = a0;
|
||||
args[1].i = a1;
|
||||
args[2].i = a2;
|
||||
args[3].i = a3;
|
||||
args[4].i = a4;
|
||||
|
||||
env->CallVoidMethodA(wrapped_obj, jDeactivateProgramAndRestoreState, args);
|
||||
AndroidBridge::HandleUncaughtException(env);
|
||||
env->PopLocalFrame(nullptr);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ public:
|
|||
static int16_t GetScreenOrientationWrapper();
|
||||
static bool GetShowPasswordSetting();
|
||||
static jintArray GetSystemColoursWrapper();
|
||||
static jstring GetUserRestrictions();
|
||||
static void HandleGeckoMessageWrapper(jobject a0);
|
||||
static void HandleUncaughtException(jobject a0, jthrowable a1);
|
||||
static void HideProgressDialog();
|
||||
|
@ -69,6 +70,7 @@ public:
|
|||
static bool IsNetworkLinkKnown();
|
||||
static bool IsNetworkLinkUp();
|
||||
static bool IsTablet();
|
||||
static bool IsUserRestricted();
|
||||
static void KillAnyZombies();
|
||||
static jclass LoadPluginClass(const nsAString& a0, const nsAString& a1);
|
||||
static void LockScreenOrientation(int32_t a0);
|
||||
|
@ -148,6 +150,7 @@ protected:
|
|||
static jmethodID jGetScreenOrientationWrapper;
|
||||
static jmethodID jGetShowPasswordSetting;
|
||||
static jmethodID jGetSystemColoursWrapper;
|
||||
static jmethodID jGetUserRestrictions;
|
||||
static jmethodID jHandleGeckoMessageWrapper;
|
||||
static jmethodID jHandleUncaughtException;
|
||||
static jmethodID jHideProgressDialog;
|
||||
|
@ -155,6 +158,7 @@ protected:
|
|||
static jmethodID jIsNetworkLinkKnown;
|
||||
static jmethodID jIsNetworkLinkUp;
|
||||
static jmethodID jIsTablet;
|
||||
static jmethodID jIsUserRestricted;
|
||||
static jmethodID jKillAnyZombies;
|
||||
static jmethodID jLoadPluginClass;
|
||||
static jmethodID jLockScreenOrientation;
|
||||
|
@ -313,7 +317,7 @@ public:
|
|||
void ActivateProgram();
|
||||
void ContentDocumentChanged();
|
||||
jobject CreateFrame();
|
||||
void DeactivateProgram();
|
||||
void DeactivateProgramAndRestoreState(bool a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4);
|
||||
jobject GetDisplayPort(bool a0, bool a1, int32_t a2, jobject a3);
|
||||
bool IsContentDocumentDisplayed();
|
||||
jobject ProgressiveUpdateCallback(bool a0, jfloat a1, jfloat a2, jfloat a3, jfloat a4, jfloat a5, bool a6);
|
||||
|
@ -327,7 +331,7 @@ protected:
|
|||
static jmethodID jActivateProgram;
|
||||
static jmethodID jContentDocumentChanged;
|
||||
static jmethodID jCreateFrame;
|
||||
static jmethodID jDeactivateProgram;
|
||||
static jmethodID jDeactivateProgramAndRestoreState;
|
||||
static jmethodID jGetDisplayPort;
|
||||
static jmethodID jIsContentDocumentDisplayed;
|
||||
static jmethodID jProgressiveUpdateCallback;
|
||||
|
|
|
@ -2273,13 +2273,15 @@ nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager, nsIntRect aRect)
|
|||
}
|
||||
|
||||
gl::GLContext* gl = static_cast<CompositorOGL*>(aManager->GetCompositor())->gl();
|
||||
gl::ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST);
|
||||
gl::ScopedScissorRect scopedScissorRectState(gl);
|
||||
bool scissorEnabled = gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST);
|
||||
GLint scissorRect[4];
|
||||
gl->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, scissorRect);
|
||||
|
||||
client->ActivateProgram();
|
||||
if (!mLayerRendererFrame.BeginDrawing(&jniFrame)) return;
|
||||
if (!mLayerRendererFrame.DrawBackground(&jniFrame)) return;
|
||||
client->DeactivateProgram(); // redundant, but in case somebody adds code after this...
|
||||
client->DeactivateProgramAndRestoreState(scissorEnabled,
|
||||
scissorRect[0], scissorRect[1], scissorRect[2], scissorRect[3]);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2300,13 +2302,15 @@ nsWindow::DrawWindowOverlay(LayerManagerComposite* aManager, nsIntRect aRect)
|
|||
mozilla::widget::android::GeckoLayerClient* client = AndroidBridge::Bridge()->GetLayerClient();
|
||||
|
||||
gl::GLContext* gl = static_cast<CompositorOGL*>(aManager->GetCompositor())->gl();
|
||||
gl::ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST);
|
||||
gl::ScopedScissorRect scopedScissorRectState(gl);
|
||||
bool scissorEnabled = gl->fIsEnabled(LOCAL_GL_SCISSOR_TEST);
|
||||
GLint scissorRect[4];
|
||||
gl->fGetIntegerv(LOCAL_GL_SCISSOR_BOX, scissorRect);
|
||||
|
||||
client->ActivateProgram();
|
||||
if (!mLayerRendererFrame.DrawForeground(&jniFrame)) return;
|
||||
if (!mLayerRendererFrame.EndDrawing(&jniFrame)) return;
|
||||
client->DeactivateProgram();
|
||||
client->DeactivateProgramAndRestoreState(scissorEnabled,
|
||||
scissorRect[0], scissorRect[1], scissorRect[2], scissorRect[3]);
|
||||
mLayerRendererFrame.Dispose(env);
|
||||
}
|
||||
|
||||
|
|