Merge m-c to inbound on a CLOSED TREE.

This commit is contained in:
Ryan VanderMeulen 2014-01-07 16:32:38 -05:00
commit 30a912783c
96 changed files with 1641 additions and 681 deletions

View File

@ -878,3 +878,13 @@ pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
// Gaia relies heavily on scroll events for now, so lets fire them
// more often than the default value (100).
pref("apz.asyncscroll.throttle", 40);
// This preference allows FirefoxOS apps (and content, I think) to force
// the use of software (instead of hardware accelerated) 2D canvases by
// creating a context like this:
//
// canvas.getContext('2d', { willReadFrequently: true })
//
// Using a software canvas can save memory when JS calls getImageData()
// on the canvas frequently. See bug 884226.
pref("gfx.canvas.willReadFrequently.enable", true);

View File

@ -8,6 +8,8 @@ let Cu = Components.utils;
let Cc = Components.classes;
let Ci = Components.interfaces;
dump("############ ErrorPage.js\n");
let ErrorPageHandler = {
_reload: function() {
docShell.QueryInterface(Ci.nsIWebNavigation).reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
@ -31,12 +33,8 @@ let ErrorPageHandler = {
}
},
domContentLoadedHandler: function(e) {
let target = e.originalTarget;
let targetDocShell = target.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation);
if (targetDocShell != docShell) {
_bindPageEvent: function(target) {
if (!target) {
return;
}
@ -52,11 +50,23 @@ let ErrorPageHandler = {
}
},
domContentLoadedHandler: function(e) {
let target = e.originalTarget;
let targetDocShell = target.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation);
if (targetDocShell != docShell) {
return;
}
this._bindPageEvent(target);
},
init: function() {
addMessageListener("ErrorPage:ReloadPage", this._reload.bind(this));
addEventListener('DOMContentLoaded',
this.domContentLoadedHandler.bind(this),
true);
this._bindPageEvent(content.document);
}
};

View File

@ -149,6 +149,26 @@ let ErrorPage = {
}
},
_listenError: function(frameLoader) {
let self = this;
let frameElement = frameLoader.ownerElement;
let injectErrorPageScript = function() {
let mm = frameLoader.messageManager;
try {
mm.loadFrameScript(kErrorPageFrameScript, true, true);
} catch (e) {
dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
}
mm.addMessageListener('ErrorPage:AddCertException', self._addCertException.bind(self));
frameElement.removeEventListener('mozbrowsererror', injectErrorPageScript, true);
};
frameElement.addEventListener('mozbrowsererror',
injectErrorPageScript,
true // use capture
);
},
init: function errorPageInit() {
Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
@ -156,17 +176,7 @@ let ErrorPage = {
observe: function errorPageObserve(aSubject, aTopic, aData) {
let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
let mm = frameLoader.messageManager;
// This won't happen from dom/ipc/preload.js in non-OOP builds.
try {
if (Services.prefs.getBoolPref("dom.ipc.tabs.disabled") === true) {
mm.loadFrameScript(kErrorPageFrameScript, true, true);
}
} catch (e) {
dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
}
mm.addMessageListener('ErrorPage:AddCertException', this._addCertException.bind(this));
this._listenError(frameLoader);
}
};

View File

@ -1,4 +1,4 @@
{
"revision": "ac80bbe24c39a813ab65b98bb56dce2e42207b22",
"revision": "d5d2b5614c32e17c5c3c736181a66bfa3fc5c3ef",
"repo_path": "/integration/gaia-central"
}

View File

@ -301,18 +301,13 @@ let DocShellCapabilitiesListener = {
_latestCapabilities: "",
init: function () {
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
gFrameTree.addObserver(this);
},
/**
* onLocationChange() is called as soon as we start loading a page after
* we are certain that there's nothing blocking the load (e.g. a content
* policy added by AdBlock or the like).
* onFrameTreeReset() is called as soon as we start loading a page.
*/
onLocationChange: function() {
onFrameTreeReset: function() {
// The order of docShell capabilities cannot change while we're running
// so calling join() without sorting before is totally sufficient.
let caps = DocShellCapabilities.collect(docShell).join(",");
@ -322,15 +317,7 @@ let DocShellCapabilitiesListener = {
this._latestCapabilities = caps;
MessageQueue.push("disallow", () => caps || null);
}
},
onStateChange: function () {},
onProgressChange: function () {},
onStatusChange: function () {},
onSecurityChange: function () {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
}
};
/**
@ -346,12 +333,12 @@ let SessionStorageListener = {
init: function () {
addEventListener("MozStorageChanged", this);
Services.obs.addObserver(this, "browser:purge-domain-data", true);
Services.obs.addObserver(this, "browser:purge-session-history", true);
gFrameTree.addObserver(this);
},
handleEvent: function (event) {
// Ignore events triggered by localStorage or globalStorage changes.
if (isSessionStorageEvent(event)) {
if (gFrameTree.contains(event.target) && isSessionStorageEvent(event)) {
this.collect();
}
},
@ -363,7 +350,17 @@ let SessionStorageListener = {
},
collect: function () {
MessageQueue.push("storage", () => SessionStorage.collect(docShell));
if (docShell) {
MessageQueue.push("storage", () => SessionStorage.collect(docShell, gFrameTree));
}
},
onFrameTreeCollected: function () {
this.collect();
},
onFrameTreeReset: function () {
this.collect();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,

View File

@ -11,7 +11,7 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
const EXPORTED_METHODS = ["addObserver", "contains", "map"];
const EXPORTED_METHODS = ["addObserver", "contains", "map", "forEach"];
/**
* A FrameTree represents all frames that were reachable when the document
@ -161,6 +161,34 @@ FrameTreeInternal.prototype = {
return walk(this.content);
},
/**
* Applies the given function |cb| to all frames stored in the tree. Use this
* method if |map()| doesn't suit your needs and you want more control over
* how data is collected.
*
* @param cb (function)
* This callback receives the current frame as the only argument.
*/
forEach: function (cb) {
let frames = this._frames;
function walk(frame) {
cb(frame);
if (!frames.has(frame)) {
return;
}
Array.forEach(frame.frames, subframe => {
if (frames.has(subframe)) {
cb(subframe);
}
});
}
walk(this.content);
},
/**
* Stores a given |frame| and its children in the frame tree.
*

View File

@ -36,6 +36,8 @@ Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
"resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
@ -102,7 +104,7 @@ const TaskUtils = {
return promise.then(
null,
function onError(reason) {
Cu.reportError("Uncaught asynchronous error: " + reason + " at\n" + reason.stack);
console.error("Uncaught asynchronous error", reason, "at", reason.stack);
throw reason;
}
);
@ -179,8 +181,7 @@ let SessionFileInternal = {
this._recordTelemetry(msg.telemetry);
} catch (ex) {
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
Cu.reportError("Could not write session state file " + this.path
+ ": " + ex);
console.error("Could not write session state file ", this.path, ex);
}
if (isFinalWrite) {
@ -193,7 +194,7 @@ let SessionFileInternal = {
SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]).then(msg => {
this._recordTelemetry(msg.telemetry);
return msg;
}, Cu.reportError);
}, console.error);
},
createBackupCopy: function (ext) {

View File

@ -17,7 +17,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
@ -309,7 +310,7 @@ let SessionSaverInternal = {
return SessionFile.write(data).then(() => {
this.updateLastSaveTime();
notify(null, "sessionstore-state-write-complete");
}, Cu.reportError);
}, console.error);
},
/**

View File

@ -12,20 +12,29 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
// Returns the principal for a given |frame| contained in a given |docShell|.
function getPrincipalForFrame(docShell, frame) {
let ssm = Services.scriptSecurityManager;
let uri = frame.document.documentURIObject;
return ssm.getDocShellCodebasePrincipal(uri, docShell);
}
this.SessionStorage = Object.freeze({
/**
* Updates all sessionStorage "super cookies"
* @param aDocShell
* @param docShell
* That tab's docshell (containing the sessionStorage)
* @param frameTree
* The docShell's FrameTree instance.
* @return Returns a nested object that will have hosts as keys and per-host
* session storage data as values. For example:
* {"example.com": {"key": "value", "my_number": 123}}
*/
collect: function (aDocShell) {
return SessionStorageInternal.collect(aDocShell);
collect: function (docShell, frameTree) {
return SessionStorageInternal.collect(docShell, frameTree);
},
/**
@ -45,36 +54,40 @@ this.SessionStorage = Object.freeze({
let SessionStorageInternal = {
/**
* Reads all session storage data from the given docShell.
* @param aDocShell
* @param docShell
* A tab's docshell (containing the sessionStorage)
* @param frameTree
* The docShell's FrameTree instance.
* @return Returns a nested object that will have hosts as keys and per-host
* session storage data as values. For example:
* {"example.com": {"key": "value", "my_number": 123}}
*/
collect: function (aDocShell) {
collect: function (docShell, frameTree) {
let data = {};
let webNavigation = aDocShell.QueryInterface(Ci.nsIWebNavigation);
let shistory = webNavigation.sessionHistory;
let visitedOrigins = new Set();
for (let i = 0; shistory && i < shistory.count; i++) {
let principal = History.getPrincipalForEntry(shistory, i, aDocShell);
frameTree.forEach(frame => {
let principal = getPrincipalForFrame(docShell, frame);
if (!principal) {
continue;
return;
}
// Get the root domain of the current history entry
// and use that as a key for the per-host storage data.
let origin = principal.jarPrefix + principal.origin;
if (data.hasOwnProperty(origin)) {
if (visitedOrigins.has(origin)) {
// Don't read a host twice.
continue;
return;
}
let originData = this._readEntry(principal, aDocShell);
// Mark the current origin as visited.
visitedOrigins.add(origin);
let originData = this._readEntry(principal, docShell);
if (Object.keys(originData).length) {
data[origin] = originData;
}
}
});
return Object.keys(data).length ? data : null;
},
@ -89,10 +102,11 @@ let SessionStorageInternal = {
* {"example.com": {"key": "value", "my_number": 123}}
*/
restore: function (aDocShell, aStorageData) {
for (let [host, data] in Iterator(aStorageData)) {
for (let host of Object.keys(aStorageData)) {
let data = aStorageData[host];
let uri = Services.io.newURI(host, null, null);
let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
// There is no need to pass documentURI, it's only used to fill documentURI property of
// domstorage event, which in this case has no consumer. Prevention of events in case
@ -104,7 +118,7 @@ let SessionStorageInternal = {
storage.setItem(key, value);
} catch (e) {
// throws e.g. for URIs that can't have sessionStorage
Cu.reportError(e);
console.error(e);
}
}
}
@ -122,7 +136,7 @@ let SessionStorageInternal = {
let storage;
try {
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
storage = storageManager.getStorage(aPrincipal);
} catch (e) {
// sessionStorage might throw if it's turned off, see bug 458954
@ -142,25 +156,3 @@ let SessionStorageInternal = {
return hostData;
}
};
let History = {
/**
* Returns a given history entry's URI.
* @param aHistory
* That tab's session history
* @param aIndex
* The history entry's index
* @param aDocShell
* That tab's docshell
*/
getPrincipalForEntry: function History_getPrincipalForEntry(aHistory,
aIndex,
aDocShell) {
try {
return Services.scriptSecurityManager.getDocShellCodebasePrincipal(
aHistory.getEntryAtIndex(aIndex, false).URI, aDocShell);
} catch (e) {
// This might throw for some reason.
}
},
};

View File

@ -102,6 +102,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1", "nsITelemetry");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
@ -959,7 +961,7 @@ let SessionStoreInternal = {
// Let everyone know we're done.
this._deferredInitialized.resolve();
}
}, Cu.reportError);
}, console.error);
};
aWindow.addEventListener("load", onload);
@ -3136,7 +3138,7 @@ let SessionStoreInternal = {
cookie.value, !!cookie.secure, !!cookie.httponly, true,
"expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
}
catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
catch (ex) { console.error(ex); } // don't let a single cookie stop recovering
}
},

View File

@ -12,6 +12,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
"resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
@ -328,7 +330,7 @@ let TabStateInternal = {
history = syncHandler.collectSessionHistory(includePrivateData);
} catch (e) {
// This may happen if the tab has crashed.
Cu.reportError(e);
console.error(e);
return tabData;
}
@ -364,7 +366,7 @@ let TabStateInternal = {
if (key != "storage" || includePrivateData) {
tabData[key] = data[key];
} else {
tabData.storage = {};
let storage = {};
let isPinned = tab.pinned;
// If we're not allowed to include private data, let's filter out hosts
@ -372,9 +374,13 @@ let TabStateInternal = {
for (let host of Object.keys(data.storage)) {
let isHttps = host.startsWith("https:");
if (PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
tabData.storage[host] = data.storage[host];
storage[host] = data.storage[host];
}
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
}
}
},

View File

@ -43,6 +43,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
@ -85,7 +87,7 @@ SessionStartup.prototype = {
SessionFile.read().then(
this._onSessionFileRead.bind(this),
Cu.reportError
console.error
);
},

View File

@ -20,6 +20,7 @@ support-files =
browser_pageStyle_sample_nested.html
browser_scrollPositions_sample.html
browser_scrollPositions_sample_frameset.html
browser_sessionStorage.html
browser_248970_b_sample.html
browser_339445_sample.html
browser_346337_sample.html

View File

@ -59,12 +59,12 @@ add_task(function flush_on_duplicate() {
"sessionStorage data has been flushed when duplicating tabs");
yield promiseTabRestored(tab2);
let {storage} = JSON.parse(ss.getTabState(tab2));
gBrowser.removeTab(tab2)
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
is(storage["http://example.com"].test, "on-duplicate",
"sessionStorage data has been flushed when duplicating tabs");
gBrowser.removeTab(tab);
gBrowser.removeTab(tab2);
});
/**

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>browser_sessionStorage.html</title>
</head>
<body>
<script type="text/javascript;version=1.8">
let isOuter = window == window.top;
let args = window.location.search.slice(1).split("&");
let rand = args[0];
if (isOuter) {
let iframe = document.createElement("iframe");
let isSecure = args.indexOf("secure") > -1;
let scheme = isSecure ? "https" : "http";
iframe.setAttribute("src", scheme + "://example.com" + location.pathname + "?" + rand);
document.body.appendChild(iframe);
}
if (sessionStorage.length === 0) {
sessionStorage.test = (isOuter ? "outer" : "inner") + "-value-" + rand;
}
</script>
</body>
</html>

View File

@ -3,35 +3,39 @@
"use strict";
let tmp = {};
Cu.import("resource://gre/modules/Promise.jsm", tmp);
let {Promise} = tmp;
const RAND = Math.random();
const URL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_sessionStorage.html" +
"?" + RAND;
const INITIAL_VALUE = "initial-value-" + Date.now();
const OUTER_VALUE = "outer-value-" + RAND;
const INNER_VALUE = "inner-value-" + RAND;
/**
* This test ensures that setting, modifying and restoring sessionStorage data
* works as expected.
*/
add_task(function session_storage() {
let tab = yield createTabWithStorageData(["http://example.com", "http://mochi.test:8888"]);
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Flush to make sure chrome received all data.
SyncHandlers.get(browser).flush();
let {storage} = JSON.parse(ss.getTabState(tab));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://example.com"].test, INNER_VALUE,
"sessionStorage data for example.com has been serialized correctly");
is(storage["http://mochi.test:8888"].test, INITIAL_VALUE,
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
"sessionStorage data for mochi.test has been serialized correctly");
// Ensure that modifying sessionStore values works.
yield modifySessionStorage(browser, {test: "modified"});
yield modifySessionStorage2(browser, {test: "modified2"});
SyncHandlers.get(browser).flush();
let {storage} = JSON.parse(ss.getTabState(tab));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://example.com"].test, "modified2",
"sessionStorage data for example.com has been serialized correctly");
is(storage["http://mochi.test:8888"].test, "modified",
"sessionStorage data for mochi.test has been serialized correctly");
@ -45,22 +49,40 @@ add_task(function session_storage() {
SyncHandlers.get(browser2).flush();
let {storage} = JSON.parse(ss.getTabState(tab2));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://example.com"].test, "modified2",
"sessionStorage data for example.com has been duplicated correctly");
is(storage["http://mochi.test:8888"].test, "modified",
"sessionStorage data for mochi.test has been duplicated correctly");
// Ensure that the content script retains restored data
// (by e.g. duplicateTab) and send it along with new data.
yield modifySessionStorage(browser2, {test: "modified2"});
// (by e.g. duplicateTab) and sends it along with new data.
yield modifySessionStorage(browser2, {test: "modified3"});
SyncHandlers.get(browser2).flush();
let {storage} = JSON.parse(ss.getTabState(tab2));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://example.com"].test, "modified2",
"sessionStorage data for example.com has been duplicated correctly");
is(storage["http://mochi.test:8888"].test, "modified2",
is(storage["http://mochi.test:8888"].test, "modified3",
"sessionStorage data for mochi.test has been duplicated correctly");
// Check that loading a new URL discards data.
browser2.loadURI("http://mochi.test:8888/");
yield promiseBrowserLoaded(browser2);
SyncHandlers.get(browser2).flush();
let {storage} = JSON.parse(ss.getTabState(tab2));
is(storage["http://mochi.test:8888"].test, "modified3",
"navigating retains correct storage data");
ok(!storage["http://example.com"], "storage data was discarded");
// Check that loading a new URL discards data.
browser2.loadURI("about:mozilla");
yield promiseBrowserLoaded(browser2);
SyncHandlers.get(browser2).flush();
let state = JSON.parse(ss.getTabState(tab2));
ok(!state.hasOwnProperty("storage"), "storage data was discarded");
// Clean up.
gBrowser.removeTab(tab);
gBrowser.removeTab(tab2);
@ -71,10 +93,12 @@ add_task(function session_storage() {
* sessionStorage data collected for tabs.
*/
add_task(function purge_domain() {
let tab = yield createTabWithStorageData(["http://example.com", "http://mochi.test:8888"]);
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
yield notifyObservers(browser, "browser:purge-domain-data", "mochi.test");
// Purge data for "mochi.test".
yield purgeDomainData(browser, "mochi.test");
// Flush to make sure chrome received all data.
SyncHandlers.get(browser).flush();
@ -82,56 +106,36 @@ add_task(function purge_domain() {
let {storage} = JSON.parse(ss.getTabState(tab));
ok(!storage["http://mochi.test:8888"],
"sessionStorage data for mochi.test has been purged");
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://example.com"].test, INNER_VALUE,
"sessionStorage data for example.com has been preserved");
gBrowser.removeTab(tab);
});
/**
* This test ensures that purging session history data also purges data from
* sessionStorage data collected for tabs
*/
add_task(function purge_shistory() {
let tab = yield createTabWithStorageData(["http://example.com", "http://mochi.test:8888"]);
let browser = tab.linkedBrowser;
yield notifyObservers(browser, "browser:purge-session-history");
// Flush to make sure chrome received all data.
SyncHandlers.get(browser).flush();
let {storage} = JSON.parse(ss.getTabState(tab));
ok(!storage["http://example.com"],
"sessionStorage data for example.com has been purged");
is(storage["http://mochi.test:8888"].test, INITIAL_VALUE,
"sessionStorage data for mochi.test has been preserved");
gBrowser.removeTab(tab);
});
/**
* This test ensures that collecting sessionStorage data respects the privacy
* levels as set by the user.
*/
add_task(function respect_privacy_level() {
let tab = yield createTabWithStorageData(["http://example.com", "https://example.com"]);
let tab = gBrowser.addTab(URL + "&secure");
yield promiseBrowserLoaded(tab.linkedBrowser);
gBrowser.removeTab(tab);
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
"http sessionStorage data has been saved");
is(storage["https://example.com"].test, INITIAL_VALUE,
is(storage["https://example.com"].test, INNER_VALUE,
"https sessionStorage data has been saved");
// Disable saving data for encrypted sites.
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
let tab = yield createTabWithStorageData(["http://example.com", "https://example.com"]);
let tab = gBrowser.addTab(URL + "&secure");
yield promiseBrowserLoaded(tab.linkedBrowser);
gBrowser.removeTab(tab);
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
"http sessionStorage data has been saved");
ok(!storage["https://example.com"],
"https sessionStorage data has *not* been saved");
@ -140,17 +144,15 @@ add_task(function respect_privacy_level() {
Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
// Check that duplicating a tab copies all private data.
let tab = yield createTabWithStorageData(["http://example.com", "https://example.com"]);
let tab = gBrowser.addTab(URL + "&secure");
yield promiseBrowserLoaded(tab.linkedBrowser);
let tab2 = gBrowser.duplicateTab(tab);
yield promiseBrowserLoaded(tab2.linkedBrowser);
yield promiseTabRestored(tab2);
gBrowser.removeTab(tab);
// With privacy_level=2 the |tab| shouldn't have any sessionStorage data.
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
ok(!storage["http://example.com"],
"http sessionStorage data has *not* been saved");
ok(!storage["https://example.com"],
"https sessionStorage data has *not* been saved");
ok(!storage, "sessionStorage data has *not* been saved");
// Restore the default privacy level and close the duplicated tab.
Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
@ -158,42 +160,26 @@ add_task(function respect_privacy_level() {
// With privacy_level=0 the duplicated |tab2| should persist all data.
let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
is(storage["http://example.com"].test, INITIAL_VALUE,
is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
"http sessionStorage data has been saved");
is(storage["https://example.com"].test, INITIAL_VALUE,
is(storage["https://example.com"].test, INNER_VALUE,
"https sessionStorage data has been saved");
});
function createTabWithStorageData(urls) {
return Task.spawn(function task() {
let tab = gBrowser.addTab();
let browser = tab.linkedBrowser;
for (let url of urls) {
browser.loadURI(url);
yield promiseBrowserLoaded(browser);
yield modifySessionStorage(browser, {test: INITIAL_VALUE});
}
throw new Task.Result(tab);
});
}
function waitForStorageEvent(browser) {
return promiseContentMessage(browser, "ss-test:MozStorageChanged");
}
function waitForUpdateMessage(browser) {
return promiseContentMessage(browser, "SessionStore:update");
}
function modifySessionStorage(browser, data) {
browser.messageManager.sendAsyncMessage("ss-test:modifySessionStorage", data);
return waitForStorageEvent(browser);
}
function notifyObservers(browser, topic, data) {
let msg = {topic: topic, data: data};
browser.messageManager.sendAsyncMessage("ss-test:notifyObservers", msg);
return waitForUpdateMessage(browser);
function modifySessionStorage2(browser, data) {
browser.messageManager.sendAsyncMessage("ss-test:modifySessionStorage2", data);
return waitForStorageEvent(browser);
}
function purgeDomainData(browser, domain) {
return sendMessage(browser, "ss-test:purgeDomainData", domain);
}

View File

@ -33,8 +33,15 @@ addMessageListener("ss-test:modifySessionStorage", function (msg) {
}
});
addMessageListener("ss-test:notifyObservers", function ({data: {topic, data}}) {
Services.obs.notifyObservers(null, topic, data || "");
addMessageListener("ss-test:modifySessionStorage2", function (msg) {
for (let key of Object.keys(msg.data)) {
content.frames[0].sessionStorage[key] = msg.data[key];
}
});
addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
Services.obs.notifyObservers(null, "browser:purge-domain-data", domain);
content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
});
addMessageListener("ss-test:getStyleSheets", function (msg) {

View File

@ -61,44 +61,59 @@ ManifestEditor.prototype = {
return this.update();
},
_onEval: function(evalString) {
let manifest = this.manifest;
eval("manifest" + evalString);
_onEval: function(variable, value) {
let parent = this._descend(variable.ownerView.symbolicPath);
try {
parent[variable.name] = JSON.parse(value);
} catch(e) {
Cu.reportError(e);
}
this.update();
},
_onSwitch: function(variable, newName) {
let manifest = this.manifest;
let newSymbolicName = variable.ownerView.symbolicName +
"['" + newName + "']";
if (newSymbolicName == variable.symbolicName) {
if (variable.name == newName) {
return;
}
let evalString = "manifest" + newSymbolicName + " = " +
"manifest" + variable.symbolicName + ";" +
"delete manifest" + variable.symbolicName;
let parent = this._descend(variable.ownerView.symbolicPath);
parent[newName] = parent[variable.name];
delete parent[variable.name];
eval(evalString);
this.update();
},
_onDelete: function(variable) {
let manifest = this.manifest;
let evalString = "delete manifest" + variable.symbolicName;
eval(evalString);
let parent = this._descend(variable.ownerView.symbolicPath);
delete parent[variable.name];
},
_onNew: function(variable, newName, newValue) {
let manifest = this.manifest;
let symbolicName = variable.symbolicName + "['" + newName + "']";
let evalString = "manifest" + symbolicName + " = " + newValue + ";";
eval(evalString);
let parent = this._descend(variable.symbolicPath);
try {
parent[newName] = JSON.parse(newValue);
} catch(e) {
Cu.reportError(e);
}
this.update();
},
/**
* Returns the value located at a given path in the manifest.
* @param path array
* A string for each path component: ["developer", "name"]
*/
_descend: function(path) {
let parent = this.manifest;
while (path.length) {
parent = parent[path.shift()];
}
return parent;
},
update: function() {
this.editor.createHierarchy();
this.editor.rawObject = this.manifest;
this.editor.commitHierarchy();

View File

@ -6,7 +6,6 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const MANIFEST_EDITOR_ENABLED = "devtools.appmanager.manifestEditor.enabled";
let gManifestWindow, gManifestEditor;
function test() {
@ -22,7 +21,9 @@ function test() {
gManifestWindow = getManifestWindow();
gManifestEditor = getProjectsWindow().UI.manifestEditor;
yield changeManifestValue("name", "the best app");
yield changeManifestValueBad("name", "the worst app");
yield addNewManifestProperty("developer", "foo", "bar");
yield addNewManifestPropertyBad("developer", "blob", "bob");
gManifestWindow = null;
gManifestEditor = null;
@ -64,6 +65,33 @@ function changeManifestValue(key, value) {
});
}
function changeManifestValueBad(key, value) {
return Task.spawn(function() {
let propElem = gManifestWindow.document
.querySelector("[id ^= '" + key + "']");
is(propElem.querySelector(".name").value, key,
"Key doesn't match expected value");
let valueElem = propElem.querySelector(".value");
EventUtils.sendMouseEvent({ type: "mousedown" }, valueElem, gManifestWindow);
let valueInput = propElem.querySelector(".element-value-input");
// Leaving out quotes will result in an error, so no change should be made.
valueInput.value = value;
EventUtils.sendKey("RETURN", gManifestWindow);
yield waitForUpdate();
// Elements have all been replaced, re-select them
propElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
valueElem = propElem.querySelector(".value");
isnot(valueElem.value, '"' + value + '"',
"Value was changed, but it should not have been");
isnot(gManifestEditor.manifest[key], value,
"Manifest was changed, but it should not have been");
});
}
function addNewManifestProperty(parent, key, value) {
return Task.spawn(function() {
let parentElem = gManifestWindow.document
@ -103,3 +131,34 @@ function addNewManifestProperty(parent, key, value) {
"Manifest doesn't contain expected value");
});
}
function addNewManifestPropertyBad(parent, key, value) {
return Task.spawn(function() {
let parentElem = gManifestWindow.document
.querySelector("[id ^= '" + parent + "']");
ok(parentElem,
"Found parent element");
let addPropertyElem = parentElem
.querySelector(".variables-view-add-property");
ok(addPropertyElem,
"Found add-property button");
EventUtils.sendMouseEvent({ type: "mousedown" }, addPropertyElem, gManifestWindow);
let nameInput = parentElem.querySelector(".element-name-input");
nameInput.value = key;
EventUtils.sendKey("TAB", gManifestWindow);
let valueInput = parentElem.querySelector(".element-value-input");
// Leaving out quotes will result in an error, so no change should be made.
valueInput.value = value;
EventUtils.sendKey("RETURN", gManifestWindow);
yield waitForUpdate();
let newElem = gManifestWindow.document.querySelector("[id ^= '" + key + "']");
ok(!newElem, "Key was added, but it should not have been");
ok(!(key in gManifestEditor.manifest[parent]),
"Manifest contains key, but it should not");
});
}

View File

@ -841,7 +841,6 @@ StackFrames.prototype = {
// Start recording any added variables or properties in any scope and
// clear existing scopes to create each one dynamically.
DebuggerView.Variables.createHierarchy();
DebuggerView.Variables.empty();
// If watch expressions evaluation results are available, create a scope
@ -1540,6 +1539,7 @@ Tracer.prototype = {
location: location,
id: this._idCounter++
};
this._stack.push(item);
DebuggerView.Tracer.addTrace({
type: "call",
@ -1598,7 +1598,7 @@ Tracer.prototype = {
getOwnPropertyNames: (callback) => {
callback({
ownPropertyNames: aObject.ownProperties
? Object.keys(aObject.ownProperties)
? Object.keys(aObject.ownProperties)
: []
});
},

View File

@ -1088,8 +1088,10 @@ function TracerView() {
DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
this._onStartTracing =
DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
this._onClear = DevToolsUtils.makeInfallible(this._onClear.bind(this));
this._onSelect = DevToolsUtils.makeInfallible(this._onSelect.bind(this));
this._onClear =
DevToolsUtils.makeInfallible(this._onClear.bind(this));
this._onSelect =
DevToolsUtils.makeInfallible(this._onSelect.bind(this));
this._onMouseOver =
DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
this._onSearch = DevToolsUtils.makeInfallible(this._onSearch.bind(this));
@ -1118,13 +1120,10 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
}
this.widget = new FastListWidget(document.getElementById("tracer-traces"));
this._traceButton.removeAttribute("hidden");
this._tracerTab.removeAttribute("hidden");
this._tracerDeck = document.getElementById("tracer-deck");
this._search = document.getElementById("tracer-search");
this._template = document.getElementsByClassName("trace-item-template")[0];
this._templateItem = this._template.getElementsByClassName("trace-item")[0];
this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
@ -1133,12 +1132,15 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
this.widget.addEventListener("select", this._onSelect, false);
this.widget.addEventListener("mouseover", this._onMouseOver, false);
this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
this._search.addEventListener("input", this._onSearch, false);
this._startTooltip = L10N.getStr("startTracingTooltip");
this._stopTooltip = L10N.getStr("stopTracingTooltip");
this._tracingNotStartedString = L10N.getStr("tracingNotStartedText");
this._noFunctionCallsString = L10N.getStr("noFunctionCallsText");
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
this.emptyText = this._tracingNotStartedString;
},
/**
@ -1171,23 +1173,38 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
/**
* Function invoked either by the "startTracing" command or by
* _onToggleTracing to start execution tracing in the backend.
*
* @return object
* A promise resolved once the tracing has successfully started.
*/
_onStartTracing: function() {
this._tracerDeck.selectedIndex = 0;
this._traceButton.setAttribute("checked", true);
this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
this.empty();
DebuggerController.Tracer.startTracing();
this.emptyText = this._noFunctionCallsString;
let deferred = promise.defer();
DebuggerController.Tracer.startTracing(deferred.resolve);
return deferred.promise;
},
/**
* Function invoked by _onToggleTracing to stop execution tracing in the
* backend.
*
* @return object
* A promise resolved once the tracing has successfully stopped.
*/
_onStopTracing: function() {
this._traceButton.removeAttribute("checked");
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
DebuggerController.Tracer.stopTracing();
this.emptyText = this._tracingNotStartedString;
let deferred = promise.defer();
DebuggerController.Tracer.stopTracing(deferred.resolve);
return deferred.promise;
},
/**
@ -1336,7 +1353,6 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
selectTab: function() {
const tabs = this._tracerTab.parentElement;
tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
this._tracerDeck.selectedIndex = 0;
},
/**
@ -1856,8 +1872,9 @@ VariableBubbleView.prototype = {
this._tooltip.setVariableContent(objectActor, {
searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
searchEnabled: Prefs.variablesSearchboxVisible,
eval: aString => {
DebuggerController.StackFrames.evaluate(aString);
eval: (variable, value) => {
let string = variable.evaluationMacro(variable, value);
DebuggerController.StackFrames.evaluate(string);
DebuggerView.VariableBubble.hideContents();
}
}, {

View File

@ -162,7 +162,10 @@ let DebuggerView = {
emptyText: L10N.getStr("emptyVariablesText"),
onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
searchEnabled: Prefs.variablesSearchboxVisible,
eval: DebuggerController.StackFrames.evaluate,
eval: (variable, value) => {
let string = variable.evaluationMacro(variable, value);
DebuggerController.StackFrames.evaluate(string);
},
lazyEmpty: true
});

View File

@ -372,36 +372,25 @@
<tabpanel id="callstack-tabpanel">
<vbox id="callstack-list" flex="1"/>
</tabpanel>
<tabpanel id="tracer-tabpanel" flex="1">
<deck id="tracer-deck" selectedIndex="1" flex="1">
<vbox flex="1">
<vbox id="tracer-traces" flex="1">
<hbox class="trace-item-template" hidden="true">
<hbox class="trace-item" align="center" flex="1" crop="end">
<label class="trace-type plain"/>
<label class="trace-name plain" crop="end"/>
</hbox>
</hbox>
</vbox>
<toolbar id="tracer-toolbar" class="devtools-toolbar">
<toolbarbutton id="clear-tracer"
label="&debuggerUI.clearButton;"
tooltiptext="&debuggerUI.clearButton.tooltip;"
command="clearTraces"
class="devtools-toolbarbutton"/>
<textbox id="tracer-search"
class="devtools-searchinput"
flex="1"
type="search"/>
</toolbar>
</vbox>
<vbox id="tracer-message"
flex="1"
align="center"
pack="center">
<description value="&debuggerUI.tracingNotStarted.label;" />
</vbox>
</deck>
<tabpanel id="tracer-tabpanel">
<vbox id="tracer-traces" flex="1"/>
<hbox class="trace-item-template" hidden="true">
<hbox class="trace-item" align="center" flex="1" crop="end">
<label class="trace-type plain"/>
<label class="trace-name plain" crop="end"/>
</hbox>
</hbox>
<toolbar id="tracer-toolbar" class="devtools-toolbar">
<toolbarbutton id="clear-tracer"
label="&debuggerUI.clearButton;"
tooltiptext="&debuggerUI.clearButton.tooltip;"
command="clearTraces"
class="devtools-toolbarbutton"/>
<textbox id="tracer-search"
class="devtools-searchinput"
flex="1"
type="search"/>
</toolbar>
</tabpanel>
</tabpanels>
</tabbox>

View File

@ -203,6 +203,7 @@ support-files =
[browser_dbg_tracing-02.js]
[browser_dbg_tracing-03.js]
[browser_dbg_tracing-04.js]
[browser_dbg_tracing-05.js]
[browser_dbg_variables-view-01.js]
[browser_dbg_variables-view-02.js]
[browser_dbg_variables-view-03.js]

View File

@ -7,7 +7,7 @@
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
let gTab, gDebuggee, gPanel, gDebugger, gVariables;
let gTab, gDebuggee, gPanel, gDebugger;
function test() {
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
@ -16,7 +16,6 @@ function test() {
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gVariables = gDebugger.DebuggerView.Variables;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(() => startTracing(gPanel))
@ -82,5 +81,4 @@ registerCleanupFunction(function() {
gDebuggee = null;
gPanel = null;
gDebugger = null;
gVariables = null;
});

View File

@ -0,0 +1,85 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that text describing the tracing state is correctly displayed.
*/
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTracer, gL10N;
function test() {
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gTracer = gDebugger.DebuggerView.Tracer;
gL10N = gDebugger.L10N;
waitForSourceShown(gPanel, "code_tracing-01.js")
.then(testTracingNotStartedText)
.then(() => gTracer._onStartTracing())
.then(testFunctionCallsUnavailableText)
.then(clickButton)
.then(() => waitForClientEvents(aPanel, "traces"))
.then(testNoEmptyText)
.then(() => gTracer._onClear())
.then(testFunctionCallsUnavailableText)
.then(() => gTracer._onStopTracing())
.then(testTracingNotStartedText)
.then(() => gTracer._onClear())
.then(testTracingNotStartedText)
.then(() => {
const deferred = promise.defer();
SpecialPowers.popPrefEnv(deferred.resolve);
return deferred.promise;
})
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
DevToolsUtils.reportException("browser_dbg_tracing-05.js", aError);
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
});
}
function testTracingNotStartedText() {
let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
ok(label,
"A label is displayed in the tracer tabpanel.");
is(label.getAttribute("value"), gL10N.getStr("tracingNotStartedText"),
"The correct {{tracingNotStartedText}} is displayed in the tracer tabpanel.");
}
function testFunctionCallsUnavailableText() {
let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
ok(label,
"A label is displayed in the tracer tabpanel.");
is(label.getAttribute("value"), gL10N.getStr("noFunctionCallsText"),
"The correct {{noFunctionCallsText}} is displayed in the tracer tabpanel.");
}
function testNoEmptyText() {
let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
ok(!label,
"No label should be displayed in the tracer tabpanel.");
}
function clickButton() {
EventUtils.sendMouseEvent({ type: "click" },
gDebuggee.document.querySelector("button"),
gDebuggee);
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gTracer = null;
gL10N = null;
});

View File

@ -45,6 +45,7 @@ const FastListWidget = module.exports = function FastListWidget(aNode) {
// Delegate some of the associated node's methods to satisfy the interface
// required by MenuContainer instances.
ViewHelpers.delegateWidgetAttributeMethods(this, aNode);
ViewHelpers.delegateWidgetEventMethods(this, aNode);
}
@ -152,18 +153,6 @@ FastListWidget.prototype = {
return this._orderedMenuElementsArray[index];
},
/**
* Returns the value of the named attribute on this container.
*
* @param string name
* The name of the attribute.
* @return string
* The current attribute value.
*/
getAttribute: function(name) {
return this._parent.getAttribute(name);
},
/**
* Adds a new attribute or changes an existing attribute on this container.
*
@ -174,6 +163,10 @@ FastListWidget.prototype = {
*/
setAttribute: function(name, value) {
this._parent.setAttribute(name, value);
if (name == "emptyText") {
this._textWhenEmpty = value;
}
},
/**
@ -184,6 +177,10 @@ FastListWidget.prototype = {
*/
removeAttribute: function(name) {
this._parent.removeAttribute(name);
if (name == "emptyText") {
this._removeEmptyText();
}
},
/**
@ -203,11 +200,51 @@ FastListWidget.prototype = {
boxObject.scrollBy(-this._list.clientWidth, 0);
},
/**
* Sets the text displayed in this container when empty.
* @param string aValue
*/
set _textWhenEmpty(aValue) {
if (this._emptyTextNode) {
this._emptyTextNode.setAttribute("value", aValue);
}
this._emptyTextValue = aValue;
this._showEmptyText();
},
/**
* Creates and appends a label signaling that this container is empty.
*/
_showEmptyText: function() {
if (this._emptyTextNode || !this._emptyTextValue) {
return;
}
let label = this.document.createElement("label");
label.className = "plain fast-list-widget-empty-text";
label.setAttribute("value", this._emptyTextValue);
this._parent.insertBefore(label, this._list);
this._emptyTextNode = label;
},
/**
* Removes the label signaling that this container is empty.
*/
_removeEmptyText: function() {
if (!this._emptyTextNode) {
return;
}
this._parent.removeChild(this._emptyTextNode);
this._emptyTextNode = null;
},
window: null,
document: null,
_parent: null,
_list: null,
_selectedItem: null,
_orderedMenuElementsArray: null,
_itemsByElement: null
_itemsByElement: null,
_emptyTextNode: null,
_emptyTextValue: ""
};

View File

@ -295,7 +295,7 @@ SideMenuWidget.prototype = {
},
/**
* Sets the text displayed in this container as a when empty.
* Sets the text displayed in this container when empty.
* @param string aValue
*/
set _textWhenEmpty(aValue) {

View File

@ -189,7 +189,7 @@ SimpleListWidget.prototype = {
},
/**
* Sets the text displayed in this container as a when empty.
* Sets the text displayed in this container when empty.
* @param string aValue
*/
set _textWhenEmpty(aValue) {

View File

@ -154,21 +154,22 @@ VariablesView.prototype = {
if (!this._store.length) {
return;
}
this._store.length = 0;
this._itemsByElement.clear();
this._prevHierarchy = this._currHierarchy;
this._currHierarchy = new Map(); // Don't clear, this is just simple swapping.
// Check if this empty operation may be executed lazily.
if (this.lazyEmpty && aTimeout > 0) {
this._emptySoon(aTimeout);
return;
}
let list = this._list;
while (list.hasChildNodes()) {
list.firstChild.remove();
while (this._list.hasChildNodes()) {
this._list.firstChild.remove();
}
this._store.length = 0;
this._itemsByElement.clear();
this._appendEmptyNotice();
this._toggleSearchVisibility(false);
},
@ -192,9 +193,6 @@ VariablesView.prototype = {
let prevList = this._list;
let currList = this._list = this.document.createElement("scrollbox");
this._store.length = 0;
this._itemsByElement.clear();
this.window.setTimeout(() => {
prevList.removeEventListener("keypress", this._onViewKeyPress, false);
prevList.removeEventListener("keydown", this._onViewKeyDown, false);
@ -1173,7 +1171,7 @@ VariablesView.getterOrSetterDeleteCallback = function(aItem) {
// Make sure the right getter/setter to value override macro is applied
// to the target object.
aItem.ownerView.eval(aItem.evaluationMacro(aItem, ""));
aItem.ownerView.eval(aItem, "");
return true; // Don't hide the element.
};
@ -2291,12 +2289,42 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
},
/**
* Gets this variable's path to the topmost scope.
* Gets this variable's path to the topmost scope in the form of a string
* meant for use via eval() or a similar approach.
* For example, a symbolic name may look like "arguments['0']['foo']['bar']".
* @return string
*/
get symbolicName() this._symbolicName,
/**
* Gets this variable's symbolic path to the topmost scope.
* @return array
* @see Variable._buildSymbolicPath
*/
get symbolicPath() {
if (this._symbolicPath) {
return this._symbolicPath;
}
this._symbolicPath = this._buildSymbolicPath();
return this._symbolicPath;
},
/**
* Build this variable's path to the topmost scope in form of an array of
* strings, one for each segment of the path.
* For example, a symbolic path may look like ["0", "foo", "bar"].
* @return array
*/
_buildSymbolicPath: function(path = []) {
if (this.name) {
path.unshift(this.name);
if (this.ownerView instanceof Variable) {
return this.ownerView._buildSymbolicPath(path);
}
}
return path;
},
/**
* Returns this variable's value from the descriptor if available.
* @return any
@ -2715,7 +2743,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
if (!this._variablesView.preventDisableOnChange) {
this._disable();
}
this.ownerView.eval(this.evaluationMacro(this, aString));
this.ownerView.eval(this, aString);
}
}, e);
},
@ -2806,6 +2834,7 @@ Variable.prototype = Heritage.extend(Scope.prototype, {
},
_symbolicName: "",
_symbolicPath: null,
_absoluteName: "",
_initialDescriptor: null,
_separatorLabel: null,
@ -2858,7 +2887,7 @@ Property.prototype["@@iterator"] = function*() {
/**
* Forget everything recorded about added scopes, variables or properties.
* @see VariablesView.createHierarchy
* @see VariablesView.commitHierarchy
*/
VariablesView.prototype.clearHierarchy = function() {
this._prevHierarchy.clear();
@ -2866,17 +2895,13 @@ VariablesView.prototype.clearHierarchy = function() {
};
/**
* Start recording a hierarchy of any added scopes, variables or properties.
* @see VariablesView.commitHierarchy
*/
VariablesView.prototype.createHierarchy = function() {
this._prevHierarchy = this._currHierarchy;
this._currHierarchy = new Map(); // Don't clear, this is just simple swapping.
};
/**
* Briefly flash the variables that changed between the previous and current
* scope/variable/property hierarchies and reopen previously expanded nodes.
* Perform operations on all the VariablesView Scopes, Variables and Properties
* after you've added all the items you wanted.
*
* Calling this method is optional, and does the following:
* - styles the items overridden by other items in parent scopes
* - reopens the items which were previously expanded
* - flashes the items whose values changed
*/
VariablesView.prototype.commitHierarchy = function() {
for (let [, currItem] of this._currHierarchy) {

View File

@ -767,7 +767,11 @@ this.WidgetMethods = {
*/
set emptyText(aValue) {
this._emptyText = aValue;
this._widget.setAttribute("emptyText", aValue);
// Apply the emptyText attribute right now if there are no child items.
if (!this._itemsByElement.size) {
this._widget.setAttribute("emptyText", aValue);
}
},
/**

View File

@ -20,6 +20,12 @@
overflow-y: auto;
}
/* FastListWidget */
.fast-list-widget-container {
overflow: auto;
}
/* SideMenuWidget */
.side-menu-widget-container {

View File

@ -3469,7 +3469,6 @@ JSTerm.prototype = {
_updateVariablesView: function JST__updateVariablesView(aOptions)
{
let view = aOptions.view;
view.createHierarchy();
view.empty();
// We need to avoid pruning the object inspection starting point.
@ -3516,20 +3515,24 @@ JSTerm.prototype = {
* @private
* @param object aOptions
* The options used for |this._updateVariablesView()|.
* @param string aString
* The string that the variables view wants to evaluate.
* @param object aVar
* The Variable object instance for the edited property.
* @param string aValue
* The value the edited property was changed to.
*/
_variablesViewEvaluate: function JST__variablesViewEvaluate(aOptions, aString)
_variablesViewEvaluate:
function JST__variablesViewEvaluate(aOptions, aVar, aValue)
{
let updater = this._updateVariablesView.bind(this, aOptions);
let onEval = this._silentEvalCallback.bind(this, updater);
let string = aVar.evaluationMacro(aVar, aValue);
let evalOptions = {
frame: this.SELECTED_FRAME,
bindObjectActor: aOptions.objectActor.actor,
};
this.requestEvaluation(aString, evalOptions).then(onEval, onEval);
this.requestEvaluation(string, evalOptions).then(onEval, onEval);
},
/**

View File

@ -333,9 +333,14 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY customizeMenu.addToPanel.label "Add to Menu">
<!ENTITY customizeMenu.addToPanel.accesskey "M">
<!ENTITY customizeMenu.moveToToolbar.label "Move to Toolbar">
<!ENTITY customizeMenu.moveToToolbar.accesskey "M">
<!ENTITY customizeMenu.moveToToolbar.accesskey "o">
<!-- LOCALIZATION NOTE (customizeMenu.moveToPanel.accesskey) can appear on the
same context menu as menubarCmd and personalbarCmd, so they should have
different access keys. customizeMenu.moveToToolbar and
customizeMenu.moveToPanel are mutually exclusive, so can share access
keys. -->
<!ENTITY customizeMenu.moveToPanel.label "Move to Menu">
<!ENTITY customizeMenu.moveToPanel.accesskey "M">
<!ENTITY customizeMenu.moveToPanel.accesskey "o">
<!ENTITY customizeMenu.removeFromToolbar.label "Remove from Toolbar">
<!ENTITY customizeMenu.removeFromToolbar.accesskey "R">
<!ENTITY customizeMenu.removeFromMenu.label "Remove from Menu">

View File

@ -50,10 +50,6 @@
- button that toggles all breakpoints for all sources. -->
<!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
<!-- LOCALIZATION NOTE (debuggerUI.tracingNotStarted.label): This is the text
- displayed when tracing hasn't started in the debugger UI. -->
<!ENTITY debuggerUI.tracingNotStarted.label "Tracing has not started.">
<!-- LOCALIZATION NOTE (debuggerUI.startTracing): This is the text displayed in
- the button to start execution tracing. -->
<!ENTITY debuggerUI.startTracing "Start Tracing">

View File

@ -85,6 +85,14 @@ noEventListenersText=No event listeners to display
# when there are no stack frames.
noStackFramesText=No stack frames to display
# LOCALIZATION NOTE (noStackFramesText): The text to display in the traces tab
# when there are no function calls.
noFunctionCallsText=No function calls to display
# LOCALIZATION NOTE (tracingNotStartedText): The text to display in the traces tab
# when when tracing hasn't started yet.
tracingNotStartedText=Tracing has not started
# LOCALIZATION NOTE (eventCheckboxTooltip): The tooltip text to display when
# the user hovers over the checkbox used to toggle an event breakpoint.
eventCheckboxTooltip=Toggle breaking on this event

View File

@ -30,7 +30,8 @@
</content>
<handlers>
<handler event="click" clickcount="1" action="this._onClick()"/>
<handler event="click" button="0" clickcount="1" action="this._onClick()"/>
<handler event="click" button="1" clickcount="1" action="event.stopPropagation(); this._onClose()"/>
<handler event="dblclick" action="this._onDoubleClick(); event.stopPropagation();"/>
</handlers>

View File

@ -32,15 +32,6 @@ function testState(aState) {
}
}
function notifyPrecise()
{
Services.obs.notifyObservers(null, "metro_precise_input", null);
}
function notifyImprecise()
{
Services.obs.notifyObservers(null, "metro_imprecise_input", null);
}
gTests.push({
desc: "precise/imprecise input switcher",

View File

@ -0,0 +1,114 @@
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
function test() {
runTests();
}
function setup() {
yield waitForCondition(function () {
return Elements.tabList && Elements.tabList.strip;
});
if (!ContextUI.tabbarVisible) {
ContextUI.displayTabs();
}
}
function tearDown() {
cleanUpOpenedTabs();
}
function isElementVisible(elm) {
return elm.ownerDocument.defaultView.getComputedStyle(elm).getPropertyValue("visibility") == "visible";
}
gTests.push({
desc: "No scrollbuttons when tab strip doesnt require overflow",
setUp: setup,
run: function () {
let tabStrip = Elements.tabList.strip;
let tabs = Elements.tabList.strip.querySelectorAll("documenttab");
// sanity check tab count:
is(tabs.length, 1, "1 tab present");
// test imprecise mode
let imprecisePromise = waitForObserver("metro_imprecise_input");
notifyImprecise();
yield imprecisePromise;
ok(!isElementVisible(tabStrip._scrollButtonUp), "left scrollbutton is hidden in imprecise mode");
ok(!isElementVisible(tabStrip._scrollButtonDown), "right scrollbutton is hidden in imprecise mode");
// test precise mode
let precisePromise = waitForObserver("metro_precise_input");
notifyPrecise();
yield precisePromise;
todo(!isElementVisible(tabStrip._scrollButtonUp), "Bug 952297 - left scrollbutton is hidden in precise mode");
ok(!isElementVisible(tabStrip._scrollButtonDown), "right scrollbutton is hidden in precise mode");
},
tearDown: tearDown
});
gTests.push({
desc: "Scrollbuttons not visible when tabstrip has overflow in imprecise input mode",
setUp: function(){
yield setup();
// ensure we're in imprecise mode
let imprecisePromise = waitForObserver("metro_imprecise_input");
notifyImprecise();
yield imprecisePromise;
},
run: function () {
// add enough tabs to get overflow in the tabstrip
let strip = Elements.tabList.strip;
ok(strip && strip.scrollClientSize && strip.scrollSize, "Sanity check tabstrip strip is present and expected properties available");
while (strip.scrollSize <= strip.scrollClientSize) {
yield addTab("about:mozilla");
}
ok(!isElementVisible(Elements.tabList.strip._scrollButtonUp), "left scrollbutton is hidden in imprecise mode");
ok(!isElementVisible(Elements.tabList.strip._scrollButtonDown), "right scrollbutton is hidden in imprecise mode");
}
});
gTests.push({
desc: "Scrollbuttons become visible when tabstrip has overflow in precise input mode",
setUp: function(){
yield setup();
// ensure we're in precise mode
let precisePromise = waitForObserver("metro_precise_input");
notifyPrecise();
yield precisePromise;
},
run: function () {
let strip = Elements.tabList.strip;
ok(strip && strip.scrollClientSize && strip.scrollSize, "Sanity check tabstrip is present and expected properties available");
// add enough tabs to get overflow in the tabstrip
while (strip.scrollSize <= strip.scrollClientSize) {
yield addTab("about:mozilla");
}
let tabs = Elements.tabList.strip.querySelectorAll("documenttab");
// select the first tab
Elements.tabs.selectedTab = tabs[0];
ok(isElementVisible(Elements.tabList.strip._scrollButtonDown), "right scrollbutton should be visible when tabList has overflow");
todo(!isElementVisible(Elements.tabList.strip._scrollButtonUp), "Bug 952297 - left scrollbutton should not visible when 1st tab is selected and tablist has overflow");
// select the last tab
Elements.tabs.selectedTab = tabs[tabs.length-1];
ok(isElementVisible(Elements.tabList.strip._scrollButtonUp), "left scrollbutton should be visible when tablist has overflow and last tab is selected");
todo(!isElementVisible(Elements.tabList.strip._scrollButtonDown), "Bug 952297 - right scrollbutton should not visible when last tab is selected and tablist has overflow");
}
});

View File

@ -556,6 +556,25 @@ function waitForObserver(aObsEvent, aTimeoutMs) {
}
}
/*=============================================================================
* Input mode helpers - these helpers notify observers to metro_precise_input
* and metro_imprecise_input respectively, triggering the same behaviour as user touch or mouse input
*
* Usage: let promise = waitForObservers("metro_imprecise_input");
* notifyImprecise();
* yield promise; // you are now in imprecise mode
*===========================================================================*/
function notifyPrecise()
{
Services.obs.notifyObservers(null, "metro_precise_input", null);
}
function notifyImprecise()
{
Services.obs.notifyObservers(null, "metro_imprecise_input", null);
}
/*=============================================================================
* Native input helpers - these helpers send input directly to the os
* generating os level input events that get processed by widget and

View File

@ -51,6 +51,7 @@ support-files =
[browser_remotetabs.js]
[browser_snappedState.js]
[browser_tabs.js]
[browser_tabs_container.js]
[browser_test.js]
[browser_tiles.js]
[browser_topsites.js]

View File

@ -1233,15 +1233,6 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
transition: height 100ms ease-out, width 100ms ease-out;
}
#social-share-button {
list-style-image: url("chrome://browser/skin/social/share-button.png");
}
#social-share-button[open],
#social-share-button:hover:active {
list-style-image: url("chrome://browser/skin/social/share-button-active.png");
}
.social-share-toolbar {
border-right: 1px solid #dedede;
background: linear-gradient(to bottom, rgba(247,247,247,.99), rgba(247,247,247,.95));

View File

@ -101,37 +101,17 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
#start-tracing {
padding: 4px;
margin: 4px;
}
#clear-tracer {
/* Make this button as narrow as the text inside it. */
min-width: 1px;
}
#tracer-message {
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
}
.trace-name {
-moz-padding-start: 4px !important;
}
#tracer-traces > scrollbox {
overflow: scroll;
/* Hack to enable hardware acceleration */
transform: translateZ(0);
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
@ -167,10 +147,6 @@
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}

View File

@ -283,6 +283,24 @@
padding: 4px 8px;
}
/* FastListWidget */
.fast-list-widget-container {
/* Hack: force hardware acceleration */
transform: translateZ(1px);
}
.theme-dark .fast-list-widget-empty-text {
padding: 12px;
font-weight: 600;
color: #fff;
}
.theme-light .fast-list-widget-empty-text {
padding: 4px 8px;
color: GrayText;
}
/* SideMenuWidget */
.side-menu-widget-container {

View File

@ -103,37 +103,17 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
#start-tracing {
padding: 4px;
margin: 4px;
}
#clear-tracer {
/* Make this button as narrow as the text inside it. */
min-width: 1px;
}
#tracer-message {
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
}
.trace-name {
-moz-padding-start: 4px !important;
}
#tracer-traces > scrollbox {
overflow: scroll;
/* Hack to enable hardware acceleration */
transform: translateZ(0);
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
@ -169,10 +149,6 @@
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}

View File

@ -283,6 +283,24 @@
padding: 4px 8px;
}
/* FastListWidget */
.fast-list-widget-container {
/* Hack: force hardware acceleration */
transform: translateZ(1px);
}
.theme-dark .fast-list-widget-empty-text {
padding: 12px;
font-weight: 600;
color: #fff;
}
.theme-light .fast-list-widget-empty-text {
padding: 4px 8px;
color: GrayText;
}
/* SideMenuWidget */
.side-menu-widget-container {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 695 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -101,37 +101,17 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
#start-tracing {
padding: 4px;
margin: 4px;
}
#clear-tracer {
/* Make this button as narrow as the text inside it. */
min-width: 1px;
}
#tracer-message {
/* Prevent the container deck from aquiring the height from this message. */
min-height: 1px;
}
.trace-name {
-moz-padding-start: 4px !important;
}
#tracer-traces > scrollbox {
overflow: scroll;
/* Hack to enable hardware acceleration */
transform: translateZ(0);
}
/* Tracer dark theme */
.theme-dark #tracer-message {
color: #f5f7fa; /* Light foreground text */
}
.theme-dark .trace-item {
color: #f5f7fa; /* Light foreground text */
}
@ -167,10 +147,6 @@
/* Tracer light theme */
.theme-light #tracer-message {
color: #292e33; /* Dark foreground text */
}
.theme-light .trace-item {
color: #292e33; /* Dark foreground text */
}

View File

@ -287,6 +287,24 @@
padding: 4px 8px;
}
/* FastListWidget */
.fast-list-widget-container {
/* Hack: force hardware acceleration */
transform: translateZ(1px);
}
.theme-dark .fast-list-widget-empty-text {
padding: 12px;
font-weight: 600;
color: #fff;
}
.theme-light .fast-list-widget-empty-text {
padding: 4px 8px;
color: GrayText;
}
/* SideMenuWidget */
.side-menu-widget-container {

View File

@ -17,6 +17,11 @@
namespace mozilla {
// The Opus format supports up to 8 channels, and supports multitrack audio up
// to 255 channels, but the current implementation supports only mono and
// stereo, and downmixes any more than that.
static const int MAX_SUPPORTED_AUDIO_CHANNELS = 8;
// http://www.opus-codec.org/docs/html_api-1.0.2/group__opus__encoder.html
// In section "opus_encoder_init", channels must be 1 or 2 of input signal.
static const int MAX_CHANNELS = 2;
@ -143,15 +148,15 @@ OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
// This monitor is used to wake up other methods that are waiting for encoder
// to be completely initialized.
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
NS_ENSURE_TRUE((aChannels <= MAX_SUPPORTED_AUDIO_CHANNELS) && (aChannels > 0),
NS_ERROR_FAILURE);
// This version of encoder API only support 1 or 2 channels,
// So set the mChannels less or equal 2 and
// let InterleaveTrackData downmix pcm data.
mChannels = aChannels > MAX_CHANNELS ? MAX_CHANNELS : aChannels;
if (aChannels <= 0) {
return NS_ERROR_FAILURE;
}
// According to www.opus-codec.org, creating an opus encoder requires the
// sampling rate of source signal be one of 8000, 12000, 16000, 24000, or
// 48000. If this constraint is not satisfied, we resample the input to 48kHz.
@ -171,6 +176,7 @@ OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
}
}
mSamplingRate = aSamplingRate;
NS_ENSURE_TRUE(mSamplingRate > 0, NS_ERROR_FAILURE);
int error = 0;
mEncoder = opus_encoder_create(GetOutputSampleRate(), mChannels,

View File

@ -41,13 +41,13 @@ protected:
nsresult Init(int aChannels, int aSamplingRate) MOZ_OVERRIDE;
private:
/**
* Get the samplerate of the data to be fed to the Opus encoder. This might be
* different from the intput samplerate if resampling occurs.
* different from the input samplerate if resampling occurs.
*/
int GetOutputSampleRate();
private:
/**
* The Opus encoder from libopus.
*/

View File

@ -0,0 +1,85 @@
/* -*- 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 "gtest/gtest.h"
#include "OpusTrackEncoder.h"
using namespace mozilla;
class TestOpusTrackEncoder : public OpusTrackEncoder
{
public:
// Return true if it has successfully initialized the Opus encoder.
bool TestOpusCreation(int aChannels, int aSamplingRate)
{
if (Init(aChannels, aSamplingRate) == NS_OK) {
if (GetPacketDuration()) {
return true;
}
}
return false;
}
// Return the sample rate of data to be fed to the Opus encoder, could be
// re-sampled if it was not one of the Opus supported sampling rates.
// Init() is expected to be called first.
int TestGetOutputSampleRate()
{
return mInitialized ? GetOutputSampleRate() : 0;
}
};
static bool
TestOpusInit(int aChannels, int aSamplingRate)
{
TestOpusTrackEncoder encoder;
return encoder.TestOpusCreation(aChannels, aSamplingRate);
}
static int
TestOpusResampler(int aChannels, int aSamplingRate)
{
TestOpusTrackEncoder encoder;
EXPECT_TRUE(encoder.TestOpusCreation(aChannels, aSamplingRate));
return encoder.TestGetOutputSampleRate();
}
TEST(Media, OpusEncoder_Init)
{
// Expect false with 0 or negative channels of input signal.
EXPECT_FALSE(TestOpusInit(0, 16000));
EXPECT_FALSE(TestOpusInit(-1, 16000));
// The Opus format supports up to 8 channels, and supports multitrack audio up
// to 255 channels, but the current implementation supports only mono and
// stereo, and downmixes any more than that.
// Expect false with channels of input signal exceed the max supported number.
EXPECT_FALSE(TestOpusInit(8 + 1, 16000));
// Should accept channels within valid range.
for (int i = 1; i <= 8; i++) {
EXPECT_TRUE(TestOpusInit(i, 16000));
}
// Expect false with 0 or negative sampling rate of input signal.
EXPECT_FALSE(TestOpusInit(1, 0));
EXPECT_FALSE(TestOpusInit(1, -1));
}
TEST(Media, OpusEncoder_Resample)
{
// Sampling rates of data to be fed to Opus encoder, should remain unchanged
// if it is one of Opus supported rates (8000, 12000, 16000, 24000 and 48000
// (kHz)) at initialization.
EXPECT_TRUE(TestOpusResampler(1, 8000) == 8000);
EXPECT_TRUE(TestOpusResampler(1, 12000) == 12000);
EXPECT_TRUE(TestOpusResampler(1, 16000) == 16000);
EXPECT_TRUE(TestOpusResampler(1, 24000) == 24000);
EXPECT_TRUE(TestOpusResampler(1, 48000) == 48000);
// Otherwise, it should be resampled to 48kHz by resampler.
EXPECT_FALSE(TestOpusResampler(1, 9600) == 9600);
EXPECT_FALSE(TestOpusResampler(1, 44100) == 44100);
}

View File

@ -0,0 +1,22 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
LIBRARY_NAME = 'media_gtest'
UNIFIED_SOURCES += [
'TestTrackEncoder.cpp',
]
LIBXUL_LIBRARY = True
EXPORT_LIBRARY = True
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/content/media/encoder',
]

View File

@ -49,7 +49,10 @@ if CONFIG['MOZ_OMX_DECODER']:
PARALLEL_DIRS += ['webspeech']
TEST_DIRS += ['test']
TEST_DIRS += [
'test',
'gtest',
]
EXPORTS += [
'AbstractMediaDecoder.h',

View File

@ -36,11 +36,8 @@ MediaOmxReader::MediaOmxReader(AbstractMediaDecoder *aDecoder) :
MediaOmxReader::~MediaOmxReader()
{
ResetDecode();
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
if (container) {
container->ClearCurrentFrame();
}
ReleaseMediaResources();
ReleaseDecoder();
mOmxDecoder.clear();
}

View File

@ -49,11 +49,6 @@ if (!('BrowserElementIsPreloaded' in this)) {
} catch (e) {
}
}
// Those are produc-specific files that's sometimes unavailable.
try {
Services.scriptloader.loadSubScript("chrome://browser/content/ErrorPage.js");
} catch (e) {
}
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
ContentPanning.init();

View File

@ -9,3 +9,4 @@ support-files =
[test_bug944397.html]
[test_bug949059.html]
[test_sendkey_cancel.html]
[test_delete_focused_element.html]

View File

@ -123,13 +123,74 @@ function test_endComposition() {
} else {
todo(false, 'endComposition changed the input field incorrectly.');
}
inputmethod_cleanup();
test_onSelectionChange();
}, function (e) {
ok(false, 'endComposition failed: ' + e.name);
inputmethod_cleanup();
});
}
function test_onSelectionChange() {
var sccTimeout = setTimeout(function() {
ok(false, 'selectionchange event not fired');
cleanup(true);
}, 3000);
function cleanup(failed) {
gContext.onselectionchange = null;
clearTimeout(sccTimeout);
if (failed) {
inputmethod_cleanup();
}
else {
test_onSurroundingTextChange();
}
}
gContext.onselectionchange = function() {
ok(true, 'onselectionchange fired');
cleanup();
};
gContext.sendKey(0, 'j'.charCodeAt(0), 0).then(function() {
// do nothing and wait for onselectionchange event
}, function(e) {
ok(false, 'sendKey failed: ' + e.name);
cleanup(true);
});
}
function test_onSurroundingTextChange() {
var sccTimeout = setTimeout(function() {
ok(false, 'surroundingtextchange event not fired');
cleanup(true);
}, 3000);
function cleanup(failed) {
gContext.onsurroundingtextchange = null;
clearTimeout(sccTimeout);
if (failed) {
inputmethod_cleanup();
}
else {
// in case we want more tests leave this
inputmethod_cleanup();
}
}
gContext.onsurroundingtextchange = function() {
ok(true, 'onsurroundingtextchange fired');
cleanup();
};
gContext.sendKey(0, 'j'.charCodeAt(0), 0).then(function() {
// do nothing and wait for onselectionchange event
}, function(e) {
ok(false, 'sendKey failed: ' + e.name);
cleanup(true);
});
}
</script>
</pre>
</body>

View File

@ -0,0 +1,107 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=952741
-->
<head>
<title>Test focused element deletion for InputMethod API.</title>
<script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=952741">Mozilla Bug 952741</a>
<input type="text" />
<p id="display"></p>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
inputmethod_setup(function() {
runTest();
});
// The frame script running in file_test_app.html.
function appFrameScript() {
let input = content.document.getElementById('test-input');
let textarea = content.document.createElement('textarea');
textarea.lang = 'en';
content.document.body.appendChild(textarea);
textarea.onfocus = function() {
textarea.parentNode.removeChild(textarea);
sendAsyncMessage('test:InputMethod:finished', {});
};
content.setTimeout(function() {
textarea.focus();
input.parentNode.removeChild(input);
}, 0);
}
function runTest() {
var timeoutId = null;
// Create an app frame to recieve keyboard inputs.
let app = document.createElement('iframe');
app.src = 'file_test_app.html';
app.setAttribute('mozbrowser', true);
document.body.appendChild(app);
app.addEventListener('mozbrowserloadend', function() {
let mm = SpecialPowers.getBrowserFrameMessageManager(app);
mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
mm.addMessageListener("test:InputMethod:finished", function() {
timeoutId = setTimeout(function() {
ok(false, 'No inputcontextchange event when textarea is deleted.');
inputmethod_cleanup();
}, 20000);
});
});
let im = navigator.mozInputMethod;
let count = 0;
im.oninputcontextchange = function() {
switch (count++) {
case 0:
if (!im.inputcontext) {
break;
}
is(im.inputcontext.lang, 'zh', 'input was focused.');
return;
case 1:
if (im.inputcontext) {
break;
}
ok(true, 'input was blurred.');
return;
case 2:
if (!im.inputcontext) {
break;
}
is(im.inputcontext.lang, 'en', 'textarea was focused.');
return;
case 3:
if (im.inputcontext) {
break;
}
ok(true, 'textarea was removed.');
clearTimeout(timeoutId);
inputmethod_cleanup();
return;
default:
return;
}
count = 100;
ok(false, 'Should not arrive here.');
inputmethod_cleanup();
};
// Set current page as an input method.
SpecialPowers.wrap(im).setActive(true);
}
</script>
</pre>
</body>
</html>

View File

@ -94,11 +94,6 @@ const BrowserElementIsPreloaded = true;
} catch (e) {
}
// Those are produc-specific files that's sometimes unavailable.
try {
Services.scriptloader.loadSubScript("chrome://browser/content/ErrorPage.js", global);
} catch (e) {
}
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js", global);
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js", global);

View File

@ -869,15 +869,12 @@ var WifiManager = (function() {
manager.state = "UNINITIALIZED";
return;
}
// This command is mandatory for Nexus 4. But some devices like
// Galaxy S2 don't support it. Continue to start wpa_supplicant
// even if we fail to set wifi operation mode to station.
gNetworkService.setWifiOperationMode(manager.ifname,
WIFI_FIRMWARE_STATION,
function (status) {
if (status) {
callback(status);
manager.state = "UNINITIALIZED";
return;
}
function doStartSupplicant() {
cancelWaitForDriverReadyTimer();
wifiCommand.startSupplicant(function (status) {

View File

@ -41,10 +41,10 @@ CheckLength(ExclusiveContext *cx, size_t length)
}
static bool
SetSourceURL(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
SetDisplayURL(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
{
if (tokenStream.hasSourceURL()) {
if (!ss->setSourceURL(cx, tokenStream.sourceURL()))
if (tokenStream.hasDisplayURL()) {
if (!ss->setDisplayURL(cx, tokenStream.displayURL()))
return false;
}
return true;
@ -368,7 +368,7 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
if (!MaybeCheckEvalFreeVariables(cx, evalCaller, scopeChain, parser, pc.ref()))
return nullptr;
if (!SetSourceURL(cx, parser.tokenStream, ss))
if (!SetDisplayURL(cx, parser.tokenStream, ss))
return nullptr;
if (!SetSourceMap(cx, parser.tokenStream, ss))
@ -597,7 +597,7 @@ CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyComp
JS_ASSERT(IsAsmJSModuleNative(fun->native()));
}
if (!SetSourceURL(cx, parser.tokenStream, ss))
if (!SetDisplayURL(cx, parser.tokenStream, ss))
return false;
if (!SetSourceMap(cx, parser.tokenStream, ss))

View File

@ -273,7 +273,7 @@ TokenStream::TokenStream(ExclusiveContext *cx, const ReadOnlyCompileOptions &opt
prevLinebase(nullptr),
userbuf(cx, base - options.column, length + options.column), // See comment below
filename(options.filename()),
sourceURL_(nullptr),
displayURL_(nullptr),
sourceMapURL_(nullptr),
tokenbuf(cx),
cx(cx),
@ -333,7 +333,7 @@ TokenStream::TokenStream(ExclusiveContext *cx, const ReadOnlyCompileOptions &opt
TokenStream::~TokenStream()
{
js_free(sourceURL_);
js_free(displayURL_);
js_free(sourceMapURL_);
JS_ASSERT_IF(originPrincipals, originPrincipals->refcount);
@ -808,7 +808,7 @@ TokenStream::getDirectives(bool isMultiline, bool shouldWarnDeprecated)
// comment. To avoid potentially expensive lookahead and backtracking, we
// only check for this case if we encounter a '#' character.
if (!getSourceURL(isMultiline, shouldWarnDeprecated))
if (!getDisplayURL(isMultiline, shouldWarnDeprecated))
return false;
if (!getSourceMappingURL(isMultiline, shouldWarnDeprecated))
return false;
@ -864,13 +864,18 @@ TokenStream::getDirective(bool isMultiline, bool shouldWarnDeprecated,
}
bool
TokenStream::getSourceURL(bool isMultiline, bool shouldWarnDeprecated)
TokenStream::getDisplayURL(bool isMultiline, bool shouldWarnDeprecated)
{
// Match comments of the form "//# sourceURL=<url>" or
// "/\* //# sourceURL=<url> *\/"
//
// Note that while these are labeled "sourceURL" in the source text,
// internally we refer to it as a "displayURL" to distinguish what the
// developer would like to refer to the source as from the source's actual
// URL.
return getDirective(isMultiline, shouldWarnDeprecated, " sourceURL=", 11,
"sourceURL", &sourceURL_);
"sourceURL", &displayURL_);
}
bool

View File

@ -628,12 +628,12 @@ class MOZ_STACK_CLASS TokenStream
return userbuf.limit();
}
bool hasSourceURL() const {
return sourceURL_ != nullptr;
bool hasDisplayURL() const {
return displayURL_ != nullptr;
}
jschar *sourceURL() {
return sourceURL_;
jschar *displayURL() {
return displayURL_;
}
bool hasSourceMapURL() const {
@ -848,7 +848,7 @@ class MOZ_STACK_CLASS TokenStream
bool getDirective(bool isMultiline, bool shouldWarnDeprecated,
const char *directive, int directiveLength,
const char *errorMsgPragma, jschar **destination);
bool getSourceURL(bool isMultiline, bool shouldWarnDeprecated);
bool getDisplayURL(bool isMultiline, bool shouldWarnDeprecated);
bool getSourceMappingURL(bool isMultiline, bool shouldWarnDeprecated);
// |expect| cannot be an EOL char.
@ -889,7 +889,7 @@ class MOZ_STACK_CLASS TokenStream
const jschar *prevLinebase; // start of previous line; nullptr if on the first line
TokenBuf userbuf; // user input buffer
const char *filename; // input filename or null
jschar *sourceURL_; // the user's requested source URL or null
jschar *displayURL_; // the user's requested source URL or null
jschar *sourceMapURL_; // source map's filename or null
CharBuffer tokenbuf; // current token string buffer
bool maybeEOL[256]; // probabilistic EOL lookup table

View File

@ -0,0 +1,39 @@
// In a debuggee with multiple scripts with varying displayURLs (aka //#
// sourceURL), findScripts can filter by displayURL.
var g = newGlobal();
g.eval("function f(){} //# sourceURL=f.js");
g.eval("function g(){} //# sourceURL=g.js");
g.eval("function h(){}");
var dbg = new Debugger();
var gw = dbg.addDebuggee(g);
var fw = gw.makeDebuggeeValue(g.f);
var ggw = gw.makeDebuggeeValue(g.g);
var hw = gw.makeDebuggeeValue(g.h);
var fScripts = dbg.findScripts({ displayURL: "f.js" });
assertEq(fScripts.indexOf(fw.script) != -1, true);
assertEq(fScripts.indexOf(ggw.script), -1);
assertEq(fScripts.indexOf(hw.script), -1);
var gScripts = dbg.findScripts({ displayURL: "g.js" });
assertEq(gScripts.indexOf(ggw.script) != -1, true);
assertEq(gScripts.indexOf(fw.script), -1);
assertEq(gScripts.indexOf(hw.script), -1);
var allScripts = dbg.findScripts();
assertEq(allScripts.indexOf(fw.script) != -1, true);
assertEq(allScripts.indexOf(ggw.script) != -1, true);
assertEq(allScripts.indexOf(hw.script) != -1, true);
try {
dbg.findScripts({ displayURL: 3 });
// Should never get here because the above line should throw
// JSMSG_UNEXPECTED_TYPE.
assertEq(true, false);
} catch(e) {
assertEq(e.name, "TypeError");
assertEq(e.message.contains("displayURL"), true);
}

View File

@ -1,5 +1,5 @@
/* -*- Mode: javascript; js-indent-level: 4; -*- */
// Source.prototype.sourceURL can be a string or null.
// Source.prototype.displayURL can be a string or null.
let g = newGlobal('new-compartment');
let dbg = new Debugger;

View File

@ -1,5 +1,5 @@
/* -*- Mode: javascript; js-indent-level: 4; -*- */
// Source.prototype.sourceURL can be a string or null.
// Source.prototype.displayURL can be a string or null.
let g = newGlobal('new-compartment');
let dbg = new Debugger;
@ -15,7 +15,7 @@ g.evaluate("function f(x) { return 2*x; }");
assertEq(getDisplayURL(), null);
// With a source url
g.evaluate("function f(x) { return 2*x; }", {sourceURL: 'file:///var/foo.js'});
g.evaluate("function f(x) { return 2*x; }", {displayURL: 'file:///var/foo.js'});
assertEq(getDisplayURL(), 'file:///var/foo.js');
// Nested functions
@ -25,7 +25,7 @@ dbg.onDebuggerStatement = function (frame) {
assertEq(frame.script.source.displayURL, 'file:///var/bar.js');
};
g.evaluate('(function () { (function () { debugger; })(); })();',
{sourceURL: 'file:///var/bar.js'});
{displayURL: 'file:///var/bar.js'});
assertEq(fired, true);
// Comment pragmas
@ -66,6 +66,6 @@ assertEq(getDisplayURL(), 'http://example.com/bar.js');
// With both a comment and the evaluate option.
g.evaluate('function f() {}\n' +
'//# sourceURL=http://example.com/foo.js',
{sourceMapURL: 'http://example.com/bar.js'});
{displayURL: 'http://example.com/bar.js'});
assertEq(getDisplayURL(), 'http://example.com/foo.js');

View File

@ -1326,7 +1326,7 @@ ScriptSource::destroy()
JS_ASSERT(ready());
adjustDataSize(0);
js_free(filename_);
js_free(sourceURL_);
js_free(displayURL_);
js_free(sourceMapURL_);
if (originPrincipals_)
JS_DropPrincipals(TlsPerThreadData.get()->runtimeFromMainThread(), originPrincipals_);
@ -1417,29 +1417,29 @@ ScriptSource::performXDR(XDRState<mode> *xdr)
sourceMapURL_[sourceMapURLLen] = '\0';
}
uint8_t haveSourceURL = hasSourceURL();
if (!xdr->codeUint8(&haveSourceURL))
uint8_t haveDisplayURL = hasDisplayURL();
if (!xdr->codeUint8(&haveDisplayURL))
return false;
if (haveSourceURL) {
uint32_t sourceURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(sourceURL_);
if (!xdr->codeUint32(&sourceURLLen))
if (haveDisplayURL) {
uint32_t displayURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(displayURL_);
if (!xdr->codeUint32(&displayURLLen))
return false;
if (mode == XDR_DECODE) {
size_t byteLen = (sourceURLLen + 1) * sizeof(jschar);
sourceURL_ = static_cast<jschar *>(xdr->cx()->malloc_(byteLen));
if (!sourceURL_)
size_t byteLen = (displayURLLen + 1) * sizeof(jschar);
displayURL_ = static_cast<jschar *>(xdr->cx()->malloc_(byteLen));
if (!displayURL_)
return false;
}
if (!xdr->codeChars(sourceURL_, sourceURLLen)) {
if (!xdr->codeChars(displayURL_, displayURLLen)) {
if (mode == XDR_DECODE) {
js_free(sourceURL_);
sourceURL_ = nullptr;
js_free(displayURL_);
displayURL_ = nullptr;
}
return false;
}
sourceURL_[sourceURLLen] = '\0';
displayURL_[displayURLLen] = '\0';
}
uint8_t haveFilename = !!filename_;
@ -1475,10 +1475,10 @@ ScriptSource::setFilename(ExclusiveContext *cx, const char *filename)
}
bool
ScriptSource::setSourceURL(ExclusiveContext *cx, const jschar *sourceURL)
ScriptSource::setDisplayURL(ExclusiveContext *cx, const jschar *displayURL)
{
JS_ASSERT(sourceURL);
if (hasSourceURL()) {
JS_ASSERT(displayURL);
if (hasDisplayURL()) {
if (cx->isJSContext() &&
!JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING,
js_GetErrorMessage, nullptr,
@ -1488,20 +1488,20 @@ ScriptSource::setSourceURL(ExclusiveContext *cx, const jschar *sourceURL)
return false;
}
}
size_t len = js_strlen(sourceURL) + 1;
size_t len = js_strlen(displayURL) + 1;
if (len == 1)
return true;
sourceURL_ = js_strdup(cx, sourceURL);
if (!sourceURL_)
displayURL_ = js_strdup(cx, displayURL);
if (!displayURL_)
return false;
return true;
}
const jschar *
ScriptSource::sourceURL()
ScriptSource::displayURL()
{
JS_ASSERT(hasSourceURL());
return sourceURL_;
JS_ASSERT(hasDisplayURL());
return displayURL_;
}
bool

View File

@ -375,7 +375,7 @@ class ScriptSource
uint32_t length_;
uint32_t compressedLength_;
char *filename_;
jschar *sourceURL_;
jschar *displayURL_;
jschar *sourceMapURL_;
JSPrincipals *originPrincipals_;
@ -392,7 +392,7 @@ class ScriptSource
length_(0),
compressedLength_(0),
filename_(nullptr),
sourceURL_(nullptr),
displayURL_(nullptr),
sourceMapURL_(nullptr),
originPrincipals_(originPrincipals),
sourceRetrievable_(false),
@ -440,10 +440,10 @@ class ScriptSource
return filename_;
}
// Source URLs
bool setSourceURL(ExclusiveContext *cx, const jschar *sourceURL);
const jschar *sourceURL();
bool hasSourceURL() const { return sourceURL_ != nullptr; }
// Display URLs
bool setDisplayURL(ExclusiveContext *cx, const jschar *displayURL);
const jschar *displayURL();
bool hasDisplayURL() const { return displayURL_ != nullptr; }
// Source maps
bool setSourceMapURL(ExclusiveContext *cx, const jschar *sourceMapURL);

View File

@ -862,7 +862,7 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp)
RootedObject element(cx);
RootedString elementProperty(cx);
JSAutoByteString fileNameBytes;
RootedString sourceURL(cx);
RootedString displayURL(cx);
RootedString sourceMapURL(cx);
unsigned lineNumber = 1;
RootedObject global(cx, nullptr);
@ -920,11 +920,11 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp)
return false;
}
if (!JS_GetProperty(cx, opts, "sourceURL", &v))
if (!JS_GetProperty(cx, opts, "displayURL", &v))
return false;
if (!v.isUndefined()) {
sourceURL = ToString(cx, v);
if (!sourceURL)
displayURL = ToString(cx, v);
if (!displayURL)
return false;
}
@ -1031,11 +1031,11 @@ Evaluate(JSContext *cx, unsigned argc, jsval *vp)
return false;
}
if (sourceURL && !script->scriptSource()->hasSourceURL()) {
const jschar *surl = JS_GetStringCharsZ(cx, sourceURL);
if (!surl)
if (displayURL && !script->scriptSource()->hasDisplayURL()) {
const jschar *durl = JS_GetStringCharsZ(cx, displayURL);
if (!durl)
return false;
if (!script->scriptSource()->setSourceURL(cx, surl))
if (!script->scriptSource()->setDisplayURL(cx, durl))
return false;
}
if (sourceMapURL && !script->scriptSource()->hasSourceMapURL()) {

View File

@ -52,6 +52,7 @@
macro(defineSetter, defineSetter, "__defineSetter__") \
macro(delete, delete_, "delete") \
macro(deleteProperty, deleteProperty, "deleteProperty") \
macro(displayURL, displayURL, "displayURL") \
macro(done, done, "done") \
macro(each, each, "each") \
macro(elementType, elementType, "elementType") \

View File

@ -2321,8 +2321,8 @@ class Debugger::ScriptQuery {
public:
/* Construct a ScriptQuery to use matching scripts for |dbg|. */
ScriptQuery(JSContext *cx, Debugger *dbg):
cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx),
innermostForCompartment(cx->runtime())
cx(cx), debugger(dbg), compartments(cx->runtime()), url(cx), displayURL(cx),
displayURLChars(nullptr), innermostForCompartment(cx->runtime())
{}
/*
@ -2419,6 +2419,16 @@ class Debugger::ScriptQuery {
}
}
/* Check for a 'displayURL' property. */
if (!JSObject::getProperty(cx, query, query, cx->names().displayURL, &displayURL))
return false;
if (!displayURL.isUndefined() && !displayURL.isString()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE,
"query object's 'displayURL' property",
"neither undefined nor a string");
return false;
}
return true;
}
@ -2427,6 +2437,7 @@ class Debugger::ScriptQuery {
url.setUndefined();
hasLine = false;
innermost = false;
displayURLChars = nullptr;
return matchAllDebuggeeGlobals();
}
@ -2490,6 +2501,14 @@ class Debugger::ScriptQuery {
/* url as a C string. */
JSAutoByteString urlCString;
/* If this is a string, matching scripts' sources have displayURLs equal to
* it. */
RootedValue displayURL;
/* displayURL as a jschar* */
const jschar *displayURLChars;
size_t displayURLLength;
/* True if the query contained a 'line' property. */
bool hasLine;
@ -2553,15 +2572,23 @@ class Debugger::ScriptQuery {
}
/*
* Given that parseQuery or omittedQuery has been called, prepare to
* match scripts. Set urlCString as appropriate.
* Given that parseQuery or omittedQuery has been called, prepare to match
* scripts. Set urlCString and displayURLChars as appropriate.
*/
bool prepareQuery() {
/* Compute urlCString, if a url was given. */
/* Compute urlCString and displayURLChars, if a url or displayURL was
* given respectively. */
if (url.isString()) {
if (!urlCString.encodeLatin1(cx, url.toString()))
return false;
}
if (displayURL.isString()) {
JSString *s = displayURL.toString();
displayURLChars = s->getChars(cx);
displayURLLength = s->length();
if (!displayURLChars)
return false;
}
return true;
}
@ -2590,6 +2617,15 @@ class Debugger::ScriptQuery {
if (line < script->lineno() || script->lineno() + js_GetScriptLineExtent(script) < line)
return;
}
if (displayURLChars) {
if (!script->scriptSource() || !script->scriptSource()->hasDisplayURL())
return;
const jschar *s = script->scriptSource()->displayURL();
if (CompareChars(s, js_strlen(s), displayURLChars, displayURLLength) != 0) {
return;
}
}
if (innermost) {
/*
* For 'innermost' queries, we don't place scripts in |vector| right
@ -3807,8 +3843,8 @@ DebuggerSource_getDisplayURL(JSContext *cx, unsigned argc, Value *vp)
ScriptSource *ss = sourceObject->source();
JS_ASSERT(ss);
if (ss->hasSourceURL()) {
JSString *str = JS_NewUCStringCopyZ(cx, ss->sourceURL());
if (ss->hasDisplayURL()) {
JSString *str = JS_NewUCStringCopyZ(cx, ss->displayURL());
if (!str)
return false;
args.rval().setString(str);

View File

@ -22,6 +22,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="@style/TabRowTextAppearance.Url"
android:textSize="14sp"/>
android:textSize="14sp"
android:maxLength="1024"/>
</LinearLayout>

View File

@ -28,7 +28,8 @@
style="@style/Widget.TwoLinePageRow.Url"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawablePadding="5dp"/>
android:drawablePadding="5dp"
android:maxLength="1024"/>
</LinearLayout>

View File

@ -19,8 +19,22 @@ public class ClientRecord extends Record {
public static final long CLIENTS_TTL = 21 * 24 * 60 * 60; // 21 days in seconds.
public static final String DEFAULT_CLIENT_NAME = "Default Name";
/**
* Each of these fields is 'owned' by the client it represents. For example,
* the "version" field is the Firefox version of that client; some time after
* that client upgrades, it'll upload a new record with its new version.
*
* The only exception is for commands. When a command is sent to a client, the
* sender will download its current record, append the command to the
* "commands" array, and reupload the record. After processing, the recipient
* will reupload its record with an empty commands array.
*
* Note that the version, then, will remain the version of the recipient, as
* with the other descriptive fields.
*/
public String name = ClientRecord.DEFAULT_CLIENT_NAME;
public String type = ClientRecord.CLIENT_TYPE;
public String version = null; // Free-form string, optional.
public JSONArray commands;
public ClientRecord(String guid, String collection, long lastModified, boolean deleted) {
@ -48,6 +62,11 @@ public class ClientRecord extends Record {
protected void initFromPayload(ExtendedJSONObject payload) {
this.name = (String) payload.get("name");
this.type = (String) payload.get("type");
try {
this.version = (String) payload.get("version");
} catch (Exception e) {
// Oh well.
}
try {
commands = payload.getArray("commands");
@ -62,6 +81,8 @@ public class ClientRecord extends Record {
putPayload(payload, "id", this.guid);
putPayload(payload, "name", this.name);
putPayload(payload, "type", this.type);
putPayload(payload, "version", this.version);
if (this.commands != null) {
payload.put("commands", this.commands);
}
@ -82,6 +103,7 @@ public class ClientRecord extends Record {
return false;
}
// Don't compare versions.
ClientRecord other = (ClientRecord) o;
if (!RepoUtils.stringsEqual(other.name, this.name) ||
!RepoUtils.stringsEqual(other.type, this.type)) {
@ -99,6 +121,7 @@ public class ClientRecord extends Record {
out.name = this.name;
out.type = this.type;
out.version = this.version;
return out;
}

View File

@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.CommandProcessor.Command;
@ -56,7 +57,7 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
protected ClientsDatabaseAccessor db;
protected volatile boolean shouldWipe;
protected volatile boolean commandsProcessedShouldUpload;
protected volatile boolean shouldUploadLocalRecord; // Set if, e.g., we received commands or need to refresh our version.
protected final AtomicInteger uploadAttemptsCount = new AtomicInteger();
protected final List<ClientRecord> toUpload = new ArrayList<ClientRecord>();
@ -183,11 +184,9 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
try {
r = (ClientRecord) factory.createRecord(record.decrypt());
if (clientsDelegate.isLocalGUID(r.guid)) {
Logger.info(LOG_TAG, "Local client GUID exists on server and was downloaded");
Logger.info(LOG_TAG, "Local client GUID exists on server and was downloaded.");
localAccountGUIDDownloaded = true;
session.config.persistServerClientRecordTimestamp(r.lastModified);
processCommands(r.commands);
handleDownloadedLocalRecord(r);
} else {
// Only need to store record if it isn't our local one.
wipeAndStore(r);
@ -268,7 +267,7 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
// If we're processing our record, we have a little more cleanup
// to do.
commandsProcessedShouldUpload = false;
shouldUploadLocalRecord = false;
session.config.persistServerClientRecordTimestamp(responseTimestamp);
session.advance();
}
@ -279,7 +278,7 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
// If upload failed because of `ifUnmodifiedSince` then there are new
// commands uploaded to our record. We must download and process them first.
if (!commandsProcessedShouldUpload ||
if (!shouldUploadLocalRecord ||
statusCode == HttpStatus.SC_PRECONDITION_FAILED ||
uploadAttemptsCount.incrementAndGet() > MAX_UPLOAD_FAILURE_COUNT) {
@ -293,7 +292,7 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
}
Logger.trace(LOG_TAG, "Retrying upload…");
// Preconditions:
// commandsProcessedShouldUpload == true &&
// shouldUploadLocalRecord == true &&
// statusCode != 412 &&
// uploadAttemptCount < MAX_UPLOAD_FAILURE_COUNT
checkAndUpload();
@ -357,12 +356,17 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
return VersionConstants.CLIENTS_ENGINE_VERSION;
}
protected String getLocalClientVersion() {
return GlobalConstants.MOZ_APP_VERSION;
}
protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) {
final String ourGUID = delegate.getAccountGUID();
final String ourName = delegate.getClientName();
ClientRecord r = new ClientRecord(ourGUID);
r.name = ourName;
r.version = getLocalClientVersion();
return r;
}
@ -373,7 +377,7 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
}
protected boolean shouldUpload() {
if (commandsProcessedShouldUpload) {
if (shouldUploadLocalRecord) {
return true;
}
@ -390,13 +394,22 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
return age >= CLIENTS_TTL_REFRESH;
}
protected void handleDownloadedLocalRecord(ClientRecord r) {
session.config.persistServerClientRecordTimestamp(r.lastModified);
if (!getLocalClientVersion().equals(r.version)) {
shouldUploadLocalRecord = true;
}
processCommands(r.commands);
}
protected void processCommands(JSONArray commands) {
if (commands == null ||
commands.size() == 0) {
return;
}
commandsProcessedShouldUpload = true;
shouldUploadLocalRecord = true;
CommandProcessor processor = CommandProcessor.getProcessor();
for (Object o : commands) {

View File

@ -13,7 +13,7 @@ import org.mozilla.gecko.FennecNativeActions;
import org.mozilla.gecko.FennecNativeDriver;
import org.mozilla.gecko.FennecTalosAssert;
import org.mozilla.gecko.tests.components.*;
import org.mozilla.gecko.tests.helpers.*;
import org.mozilla.gecko.tests.helpers.HelperInitializer;
import com.jayway.android.robotium.solo.Solo;
@ -121,13 +121,7 @@ abstract class UITest extends ActivityInstrumentationTestCase2<Activity>
}
private void initHelpers() {
// Other helpers make assertions so init AssertionHelper first.
AssertionHelper.init(this);
DeviceHelper.init(this);
GeckoHelper.init(this);
NavigationHelper.init(this);
WaitHelper.init(this);
HelperInitializer.init(this);
}
@Override

View File

@ -18,7 +18,7 @@ public final class AssertionHelper {
private AssertionHelper() { /* To disallow instantation. */ }
public static void init(final UITestContext context) {
protected static void init(final UITestContext context) {
sAsserter = context.getAsserter();
}

View File

@ -47,7 +47,7 @@ public final class DeviceHelper {
assertTrue("The device is a tablet", isTablet());
}
public static void init(final UITestContext context) {
protected static void init(final UITestContext context) {
sActivity = context.getActivity();
sSolo = context.getSolo();

View File

@ -21,7 +21,7 @@ public final class GeckoHelper {
private GeckoHelper() { /* To disallow instantiation. */ }
public static void init(final UITestContext context) {
protected static void init(final UITestContext context) {
sActivity = context.getActivity();
sActions = context.getActions();
}

View File

@ -0,0 +1,27 @@
/* 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/. */
package org.mozilla.gecko.tests.helpers;
import org.mozilla.gecko.tests.UITestContext;
/**
* AssertionHelper is statically imported in many places. Thus we want to hide
* its init method outside of this package. We initialize the remaining helper
* classes from here so that all the init methods are package protected.
*/
public final class HelperInitializer {
private HelperInitializer() { /* To disallow instantiation. */ }
public static void init(final UITestContext context) {
// Other helpers make assertions so init AssertionHelper first.
AssertionHelper.init(context);
DeviceHelper.init(context);
GeckoHelper.init(context);
NavigationHelper.init(context);
WaitHelper.init(context);
}
}

View File

@ -24,7 +24,7 @@ final public class NavigationHelper {
private static ToolbarComponent sToolbar;
public static void init(final UITestContext context) {
protected static void init(final UITestContext context) {
sContext = context;
sSolo = context.getSolo();

View File

@ -40,7 +40,7 @@ public final class WaitHelper {
private WaitHelper() { /* To disallow instantiation. */ }
public static void init(final UITestContext context) {
protected static void init(final UITestContext context) {
sContext = context;
sSolo = context.getSolo();
sActions = context.getActions();

View File

@ -75,13 +75,7 @@ var SelectionHandler = {
observe: function sh_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "Gesture:SingleTap": {
if (this._activeType == this.TYPE_SELECTION) {
let data = JSON.parse(aData);
if (this._pointInSelection(data.x, data.y))
this.copySelection();
else
this._closeSelection();
} else if (this._activeType == this.TYPE_CURSOR) {
if (this._activeType == this.TYPE_CURSOR) {
// attachCaret() is called in the "Gesture:SingleTap" handler in BrowserEventHandler
// We're guaranteed to call this first, because this observer was added last
this._deactivate();
@ -239,21 +233,14 @@ var SelectionHandler = {
// Clear any existing selection from the document
this._contentWindow.getSelection().removeAllRanges();
if (aOptions.mode == this.SELECT_ALL) {
this._getSelectionController().selectAll();
} else if (aOptions.mode == this.SELECT_AT_POINT) {
if (!this._domWinUtils.selectAtPoint(aOptions.x, aOptions.y, Ci.nsIDOMWindowUtils.SELECT_WORDNOSPACE)) {
this._deactivate();
return false;
}
} else {
Services.console.logStringMessage("Invalid selection mode " + aOptions.mode);
// Perform the appropriate selection method, if we can't determine method, or it fails, return
if (!this._performSelection(aOptions)) {
this._deactivate();
return false;
}
// Double check results of successful selection operation
let selection = this._getSelection();
// If the range didn't have any text, let's bail
if (!selection || selection.rangeCount == 0 || selection.getRangeAt(0).collapsed) {
this._deactivate();
return false;
@ -283,6 +270,29 @@ var SelectionHandler = {
return true;
},
/*
* Called to perform a selection operation, given a target element, selection method, starting point etc.
*/
_performSelection: function sh_performSelection(aOptions) {
if (aOptions.mode == this.SELECT_ALL) {
if (this._targetElement instanceof HTMLPreElement) {
// Use SELECT_PARAGRAPH else we default to entire page including trailing whitespace
return this._domWinUtils.selectAtPoint(1, 1, Ci.nsIDOMWindowUtils.SELECT_PARAGRAPH);
} else {
// Else default to selectALL Document
this._getSelectionController().selectAll();
return true;
}
}
if (aOptions.mode == this.SELECT_AT_POINT) {
return this._domWinUtils.selectAtPoint(aOptions.x, aOptions.y, Ci.nsIDOMWindowUtils.SELECT_WORDNOSPACE);
}
Services.console.logStringMessage("Invalid selection mode " + aOptions.mode);
return false;
},
/* Return true if the current selection (given by aPositions) is near to where the coordinates passed in */
_selectionNearClick: function(aX, aY, aPositions) {
let distance = 0;
@ -517,14 +527,7 @@ var SelectionHandler = {
},
selectAll: function sh_selectAll(aElement) {
if (this._activeType != this.TYPE_SELECTION) {
this.startSelection(aElement, { mode : this.SELECT_ALL });
} else {
let selectionController = this._getSelectionController();
selectionController.selectAll();
this._updateCacheForSelection();
this._positionHandles();
}
this.startSelection(aElement, { mode : this.SELECT_ALL });
},
/*

View File

@ -0,0 +1,116 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
function dump(a) {
Services.console.logStringMessage(a);
}
function sendMessageToJava(aMessage) {
Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}
function init() {
let sumoLink = Services.urlFormatter.formatURLPref("app.support.baseURL");
document.getElementById("sumo-link").href = sumoLink;
window.addEventListener("popstate", function (aEvent) {
updateActiveSection(aEvent.state ? aEvent.state.section : "intro")
}, false);
// Fill "Last visited site" input with most recent history entry URL.
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
document.getElementById("last-url").value = aData;
}, "Feedback:LastUrl", false);
sendMessageToJava({ type: "Feedback:LastUrl" });
}
function uninit() {
Services.obs.removeObserver(this, "Feedback:LastUrl");
}
function switchSection(aSection) {
history.pushState({ section: aSection }, aSection);
updateActiveSection(aSection);
}
function updateActiveSection(aSection) {
document.querySelector("section[active]").removeAttribute("active");
document.getElementById(aSection).setAttribute("active", true);
}
function openPlayStore() {
sendMessageToJava({ type: "Feedback:OpenPlayStore" });
window.close();
}
function maybeLater() {
window.close();
sendMessageToJava({ type: "Feedback:MaybeLater" });
}
function sendFeedback(aEvent) {
// Prevent the page from reloading.
aEvent.preventDefault();
let section = history.state.section;
// Sanity check.
if (section != "sad") {
Cu.reportError("Trying to send feedback from an invalid section: " + section);
return;
}
let sectionElement = document.getElementById(section);
let descriptionElement = sectionElement.querySelector(".description");
// Bail if the description value isn't valid. HTML5 form validation will take care
// of showing an error message for us.
if (!descriptionElement.validity.valid)
return;
let data = new FormData();
data.append("description", descriptionElement.value);
data.append("_type", 2);
let urlElement = document.getElementById("last-url");
// Bail if the URL value isn't valid. HTML5 form validation will take care
// of showing an error message for us.
if (!urlElement.validity.valid)
return;
// Only send a URL string if the user provided one.
if (urlElement.value) {
data.append("add_url", true);
data.append("url", urlElement.value);
}
let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
data.append("device", sysInfo.get("device"));
data.append("manufacturer", sysInfo.get("manufacturer"));
let req = new XMLHttpRequest();
req.addEventListener("error", function() {
Cu.reportError("Error sending feedback to input.mozilla.org: " + req.statusText);
}, false);
req.addEventListener("abort", function() {
Cu.reportError("Aborted sending feedback to input.mozilla.org: " + req.statusText);
}, false);
let postURL = Services.urlFormatter.formatURLPref("app.feedback.postURL");
req.open("POST", postURL, true);
req.send(data);
switchSection("thanks-" + section);
}

View File

@ -74,118 +74,6 @@
</div>
</section>
<script type="application/javascript;version=1.8"><![CDATA[
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
function dump(a) {
Services.console.logStringMessage(a);
}
function sendMessageToJava(aMessage) {
Services.androidBridge.handleGeckoMessage(JSON.stringify(aMessage));
}
function init() {
let sumoLink = Services.urlFormatter.formatURLPref("app.support.baseURL");
document.getElementById("sumo-link").href = sumoLink;
window.addEventListener("popstate", function (aEvent) {
updateActiveSection(aEvent.state ? aEvent.state.section : "intro")
}, false);
// Fill "Last visited site" input with most recent history entry URL.
Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
document.getElementById("last-url").value = aData;
}, "Feedback:LastUrl", false);
sendMessageToJava({ type: "Feedback:LastUrl" });
}
function uninit() {
Services.obs.removeObserver(this, "Feedback:LastUrl");
}
function switchSection(aSection) {
history.pushState({ section: aSection }, aSection);
updateActiveSection(aSection);
}
function updateActiveSection(aSection) {
document.querySelector("section[active]").removeAttribute("active");
document.getElementById(aSection).setAttribute("active", true);
}
function openPlayStore() {
sendMessageToJava({ type: "Feedback:OpenPlayStore" });
window.close();
}
function maybeLater() {
window.close();
sendMessageToJava({ type: "Feedback:MaybeLater" });
}
function sendFeedback(aEvent) {
// Prevent the page from reloading.
aEvent.preventDefault();
let section = history.state.section;
// Sanity check.
if (section != "sad") {
Cu.reportError("Trying to send feedback from an invalid section: " + section);
return;
}
let sectionElement = document.getElementById(section);
let descriptionElement = sectionElement.querySelector(".description");
// Bail if the description value isn't valid. HTML5 form validation will take care
// of showing an error message for us.
if (!descriptionElement.validity.valid)
return;
let data = new FormData();
data.append("description", descriptionElement.value);
data.append("_type", 2);
let urlElement = document.getElementById("last-url");
// Bail if the URL value isn't valid. HTML5 form validation will take care
// of showing an error message for us.
if (!urlElement.validity.valid)
return;
// Only send a URL string if the user provided one.
if (urlElement.value) {
data.append("add_url", true);
data.append("url", urlElement.value);
}
let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
data.append("device", sysInfo.get("device"));
data.append("manufacturer", sysInfo.get("manufacturer"));
let req = new XMLHttpRequest();
req.addEventListener("error", function() {
Cu.reportError("Error sending feedback to input.mozilla.org: " + req.statusText);
}, false);
req.addEventListener("abort", function() {
Cu.reportError("Aborted sending feedback to input.mozilla.org: " + req.statusText);
}, false);
let postURL = Services.urlFormatter.formatURLPref("app.feedback.postURL");
req.open("POST", postURL, true);
req.send(data);
switchSection("thanks-" + section);
}
]]></script>
<script type="application/javascript;version=1.8" src="chrome://browser/content/aboutFeedback.js"></script>
</body>
</html>

View File

@ -16,6 +16,7 @@ chrome.jar:
content/aboutDownloads.xhtml (content/aboutDownloads.xhtml)
content/aboutDownloads.js (content/aboutDownloads.js)
content/aboutFeedback.xhtml (content/aboutFeedback.xhtml)
content/aboutFeedback.js (content/aboutFeedback.js)
content/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
content/aboutReader.html (content/aboutReader.html)
content/aboutReader.js (content/aboutReader.js)

View File

@ -304,12 +304,14 @@ TabTracker.prototype = {
switch (topic) {
case "domwindowopened":
// Add tab listeners now that a window has opened.
subject.addEventListener("load", (event) => {
let onLoad = () => {
subject.removeEventListener("load", onLoad, false);
// Only register after the window is done loading to avoid unloads.
this._registerListenersForWindow(subject);
}, false);
};
// Add tab listeners now that a window has opened.
subject.addEventListener("load", onLoad, false);
break;
}
},

View File

@ -8,6 +8,10 @@ if (typeof(Ci) == 'undefined') {
var Ci = Components.interfaces;
}
if (typeof(Cc) == 'undefined') {
var Cc = Components.classes;
}
/**
* Special Powers Exception - used to throw exceptions nicely
**/

View File

@ -489,7 +489,7 @@ ThreadActor.prototype = {
},
get youngestFrame() {
if (!this.state == "paused") {
if (this.state != "paused") {
return null;
}
return this.dbg.getNewestFrame();

View File

@ -1014,8 +1014,12 @@ DebuggerServerConnection.prototype = {
}
var ret = null;
// Dispatch the request to the actor.
if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
// handle "requestTypes" RDP request.
if (aPacket.type == "requestTypes") {
ret = { from: actor.actorID, requestTypes: Object.keys(actor.requestTypes) };
} else if (actor.requestTypes && actor.requestTypes[aPacket.type]) {
// Dispatch the request to the actor.
try {
this.currentPacket = aPacket;
ret = actor.requestTypes[aPacket.type].bind(actor)(aPacket, this);

View File

@ -0,0 +1,52 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let promise = devtools.require("sdk/core/promise");
function test_requestTypes_request(aClient, anActor)
{
var calls = [];
calls.push(test_existent_actor(aClient, anActor));
promise.all(calls).then(() => {
aClient.close(() => {
do_test_finished();
});
});
}
function test_existent_actor(aClient, anActor)
{
let deferred = promise.defer();
aClient.request({ to: anActor, type: "requestTypes" }, function (aResponse) {
var expectedRequestTypes = Object.keys(DebuggerServer.
globalActorFactories["chromeDebugger"].
prototype.requestTypes);
do_check_true(Array.isArray(aResponse.requestTypes));
do_check_eq(JSON.stringify(aResponse.requestTypes),
JSON.stringify(expectedRequestTypes));
deferred.resolve();
});
return deferred.promise;
}
function run_test()
{
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(function() {
client.listTabs(function(aResponse) {
test_requestTypes_request(client, aResponse.chromeDebugger);
});
});
do_test_pending();
}

View File

@ -193,3 +193,5 @@ reason = bug 820380
[test_trace_actor-08.js]
[test_trace_actor-09.js]
[test_ignore_caught_exceptions.js]
[test_requestTypes.js]
reason = bug 937197

View File

@ -245,7 +245,7 @@ let NetworkHelper = {
Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
NetUtil.asyncFetch(channel, function (aInputStream, aStatusCode, aRequest) {
NetUtil.asyncFetch(channel, (aInputStream, aStatusCode, aRequest) => {
if (!components.isSuccessCode(aStatusCode)) {
aCallback(null);
return;

View File

@ -448,6 +448,7 @@ COMPONENT_LIBS += \
gfxtest \
ssltest \
xpcom_glue_gtest \
media_gtest \
$(NULL)
endif

View File

@ -145,21 +145,58 @@ struct UserInputData {
::Touch touches[MAX_POINTERS];
} motion;
};
Modifiers DOMModifiers() const;
};
Modifiers
UserInputData::DOMModifiers() const
{
Modifiers result = 0;
if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
result |= MODIFIER_ALT;
}
if (metaState & (AMETA_SHIFT_ON |
AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
result |= MODIFIER_SHIFT;
}
if (metaState & AMETA_FUNCTION_ON) {
result |= MODIFIER_FN;
}
if (metaState & (AMETA_CTRL_ON |
AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
result |= MODIFIER_CONTROL;
}
if (metaState & (AMETA_META_ON |
AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
result |= MODIFIER_META;
}
if (metaState & AMETA_CAPS_LOCK_ON) {
result |= MODIFIER_CAPSLOCK;
}
if (metaState & AMETA_NUM_LOCK_ON) {
result |= MODIFIER_NUMLOCK;
}
if (metaState & AMETA_SCROLL_LOCK_ON) {
result |= MODIFIER_SCROLLLOCK;
}
return result;
}
static void
sendMouseEvent(uint32_t msg, uint64_t timeMs, int x, int y, bool forwardToChildren)
sendMouseEvent(uint32_t msg, UserInputData& data, bool forwardToChildren)
{
WidgetMouseEvent event(true, msg, nullptr,
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
event.refPoint.x = x;
event.refPoint.y = y;
event.time = timeMs;
event.refPoint.x = data.motion.touches[0].coords.getX();
event.refPoint.y = data.motion.touches[0].coords.getY();
event.time = data.timeMs;
event.button = WidgetMouseEvent::eLeftButton;
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
if (msg != NS_MOUSE_MOVE)
event.clickCount = 1;
event.modifiers = data.DOMModifiers();
event.mFlags.mNoCrossProcessBoundaryForwarding = !forwardToChildren;
@ -206,6 +243,7 @@ sendTouchEvent(UserInputData& data, bool* captured)
WidgetTouchEvent event(true, msg, nullptr);
event.time = data.timeMs;
event.modifiers = data.DOMModifiers();
int32_t i;
if (msg == NS_TOUCH_END) {
@ -231,6 +269,9 @@ private:
const UserInputData& mData;
sp<KeyCharacterMap> mKeyCharMap;
char16_t mChar;
char16_t mUnmodifiedChar;
uint32_t mDOMKeyCode;
KeyNameIndex mDOMKeyNameIndex;
char16_t mDOMPrintableKeyValue;
@ -244,9 +285,21 @@ private:
return IsKeyPress() && (mData.flags & AKEY_EVENT_FLAG_LONG_PRESS);
}
uint32_t CharCode() const;
char16_t PrintableKeyValue() const;
int32_t UnmodifiedMetaState() const
{
return mData.metaState &
~(AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON |
AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON |
AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
}
static bool IsControlChar(char16_t aChar)
{
return (aChar < ' ' || aChar == 0x7F);
}
void DispatchKeyDownEvent();
void DispatchKeyUpEvent();
nsEventStatus DispatchKeyEventInternal(uint32_t aEventMessage);
@ -254,47 +307,44 @@ private:
KeyEventDispatcher::KeyEventDispatcher(const UserInputData& aData,
KeyCharacterMap* aKeyCharMap) :
mData(aData), mKeyCharMap(aKeyCharMap)
mData(aData), mKeyCharMap(aKeyCharMap), mChar(0), mUnmodifiedChar(0),
mDOMPrintableKeyValue(0)
{
// XXX Printable key's keyCode value should be computed with actual
// input character.
mDOMKeyCode = (mData.key.keyCode < ArrayLength(kKeyMapping)) ?
kKeyMapping[mData.key.keyCode] : 0;
mDOMKeyNameIndex = GetKeyNameIndex(mData.key.keyCode);
mDOMPrintableKeyValue = PrintableKeyValue();
}
uint32_t
KeyEventDispatcher::CharCode() const
{
if (!mKeyCharMap.get()) {
return 0;
return;
}
// XXX If the charCode is not a printable character, the charCode should be
// computed without Ctrl/Alt/Meta modifiers.
char16_t ch = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState);
return (ch >= ' ') ? static_cast<uint32_t>(ch) : 0;
mChar = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState);
if (IsControlChar(mChar)) {
mChar = 0;
}
int32_t unmodifiedMetaState = UnmodifiedMetaState();
if (mData.metaState == unmodifiedMetaState) {
mUnmodifiedChar = mChar;
} else {
mUnmodifiedChar = mKeyCharMap->getCharacter(mData.key.keyCode,
unmodifiedMetaState);
if (IsControlChar(mUnmodifiedChar)) {
mUnmodifiedChar = 0;
}
}
mDOMPrintableKeyValue = PrintableKeyValue();
}
char16_t
KeyEventDispatcher::PrintableKeyValue() const
{
if (mDOMKeyNameIndex != KEY_NAME_INDEX_USE_STRING || !mKeyCharMap.get()) {
if (mDOMKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
return 0;
}
char16_t ch = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState);
if (ch >= ' ') {
return static_cast<char16_t>(ch);
}
int32_t unmodifiedMetaState = mData.metaState &
~(AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON |
AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON |
AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
if (unmodifiedMetaState == mData.metaState) {
return 0;
}
ch = mKeyCharMap->getCharacter(mData.key.keyCode, unmodifiedMetaState);
return (ch >= ' ') ? static_cast<char16_t>(ch) : 0;
return mChar ? mChar : mUnmodifiedChar;
}
nsEventStatus
@ -302,7 +352,9 @@ KeyEventDispatcher::DispatchKeyEventInternal(uint32_t aEventMessage)
{
WidgetKeyboardEvent event(true, aEventMessage, nullptr);
if (aEventMessage == NS_KEY_PRESS) {
event.charCode = CharCode();
// XXX If the charCode is not a printable character, the charCode
// should be computed without Ctrl/Alt/Meta modifiers.
event.charCode = static_cast<uint32_t>(mChar);
}
if (!event.charCode) {
event.keyCode = mDOMKeyCode;
@ -313,6 +365,7 @@ KeyEventDispatcher::DispatchKeyEventInternal(uint32_t aEventMessage)
if (mDOMPrintableKeyValue) {
event.mKeyValue = mDOMPrintableKeyValue;
}
event.modifiers = mData.DOMModifiers();
event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE;
event.time = mData.timeMs;
return nsWindow::DispatchInputEvent(event);
@ -634,11 +687,7 @@ GeckoInputDispatcher::dispatchOnce()
msg = NS_MOUSE_BUTTON_UP;
break;
}
sendMouseEvent(msg,
data.timeMs,
data.motion.touches[0].coords.getX(),
data.motion.touches[0].coords.getY(),
status != nsEventStatus_eConsumeNoDefault);
sendMouseEvent(msg, data, status != nsEventStatus_eConsumeNoDefault);
break;
}
case UserInputData::KEY_DATA: {