merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2014-10-20 14:48:28 +02:00
commit 1b9bf0226b
81 changed files with 1254 additions and 541 deletions

View File

@ -240,6 +240,11 @@
label="&emailVideoCmd.label;"
accesskey="&emailVideoCmd.accesskey;"
oncommand="gContextMenu.sendMedia();"/>
<menu id="context-castvideo"
label="&castVideoCmd.label;"
accesskey="&castVideoCmd.accesskey;">
<menupopup id="context-castvideo-popup" onpopupshowing="gContextMenu.populateCastVideoMenu(this)"/>
</menu>
<menuitem id="context-sendaudio"
label="&emailAudioCmd.label;"
accesskey="&emailAudioCmd.accesskey;"

View File

@ -47,10 +47,6 @@ var tabPreviews = {
},
capture: function tabPreviews_capture(aTab, aShouldCache) {
// Bug 863512 - Make page thumbnails work in electrolysis
if (gMultiProcessBrowser)
return new Image();
let browser = aTab.linkedBrowser;
let uri = browser.currentURI.spec;
@ -77,7 +73,7 @@ var tabPreviews = {
aTab.__thumbnail_lastURI = uri;
}
PageThumbs.captureToCanvas(aTab.linkedBrowser.contentWindow, canvas);
PageThumbs.captureToCanvas(browser, canvas);
return canvas;
},

View File

@ -33,10 +33,6 @@ let gBrowserThumbnails = {
_tabEvents: ["TabClose", "TabSelect"],
init: function Thumbnails_init() {
// Bug 863512 - Make page thumbnails work in electrolysis
if (gMultiProcessBrowser)
return;
PageThumbs.addExpirationFilter(this);
gBrowser.addTabsProgressListener(this);
Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
@ -52,10 +48,6 @@ let gBrowserThumbnails = {
},
uninit: function Thumbnails_uninit() {
// Bug 863512 - Make page thumbnails work in electrolysis
if (gMultiProcessBrowser)
return;
PageThumbs.removeExpirationFilter(this);
gBrowser.removeTabsProgressListener(this);
Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
@ -125,10 +117,6 @@ let gBrowserThumbnails = {
// FIXME: This should be part of the PageThumbs API. (bug 1062414)
_shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
// Don't try to capture in e10s yet (because of bug 698371)
if (gMultiProcessBrowser)
return false;
// Capture only if it's the currently selected tab.
if (aBrowser != gBrowser.selectedBrowser)
return false;
@ -144,14 +132,18 @@ let gBrowserThumbnails = {
if (doc instanceof SVGDocument || doc instanceof XMLDocument)
return false;
// There's no point in taking screenshot of loading pages.
if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
return false;
// Don't take screenshots of about: pages.
if (aBrowser.currentURI.schemeIs("about"))
return false;
// FIXME e10s work around, we need channel information. bug 1073957
if (!aBrowser.docShell)
return true;
// There's no point in taking screenshot of loading pages.
if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
return false;
let channel = aBrowser.docShell.currentDocumentChannel;
// No valid document channel. We shouldn't take a screenshot.

View File

@ -188,6 +188,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
XPCOMUtils.defineLazyModuleGetter(this, "UITour",
"resource:///modules/UITour.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CastingApps",
"resource:///modules/CastingApps.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
"resource://gre/modules/SimpleServiceDiscovery.jsm");
let gInitialPages = [
"about:blank",
"about:newtab",

View File

@ -208,9 +208,19 @@ nsContextMenu.prototype = {
// Send media URL (but not for canvas, since it's a big data: URL)
this.showItem("context-sendimage", this.onImage);
this.showItem("context-sendvideo", this.onVideo);
this.showItem("context-castvideo", this.onVideo);
this.showItem("context-sendaudio", this.onAudio);
this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
// getServicesForVideo alone would be sufficient here (it depends on
// SimpleServiceDiscovery.services), but SimpleServiceDiscovery is garanteed
// to be already loaded, since we load it on startup, and CastingApps isn't,
// so check SimpleServiceDiscovery.services first to avoid needing to load
// CastingApps.jsm if we don't need to.
let shouldShowCast = this.mediaURL &&
SimpleServiceDiscovery.services.length > 0 &&
CastingApps.getServicesForVideo(this.target).length > 0;
this.setItemAttr("context-castvideo", "disabled", !shouldShowCast);
},
initViewItems: function CM_initViewItems() {
@ -1316,6 +1326,25 @@ nsContextMenu.prototype = {
MailIntegration.sendMessage(this.mediaURL, "");
},
castVideo: function() {
CastingApps.openExternal(this.target, window);
},
populateCastVideoMenu: function(popup) {
let videoEl = this.target;
popup.innerHTML = null;
let doc = popup.ownerDocument;
let services = CastingApps.getServicesForVideo(videoEl);
services.forEach(service => {
let item = doc.createElement("menuitem");
item.setAttribute("label", service.friendlyName);
item.addEventListener("command", event => {
CastingApps.sendVideoToService(videoEl, service);
});
popup.appendChild(item);
});
},
playPlugin: function() {
gPluginHandler.contextMenuCommand(this.browser, this.target, "play");
},

View File

@ -707,7 +707,7 @@
canvas.mozOpaque = true;
canvas.width = 160 * scale;
canvas.height = 90 * scale;
PageThumbs.captureToCanvas(chatbox.contentWindow, canvas);
PageThumbs.captureToCanvas(chatbox, canvas);
dt.setDragImage(canvas, -16 * scale, -16 * scale);
event.stopPropagation();

View File

@ -4530,7 +4530,7 @@
canvas.height = 90 * scale;
if (!gMultiProcessBrowser) {
// Bug 863512 - Make page thumbnails work in e10s
PageThumbs.captureToCanvas(browser.contentWindow, canvas);
PageThumbs.captureToCanvas(browser, canvas);
}
dt.setDragImage(canvas, -16 * scale, -16 * scale);

View File

@ -174,7 +174,9 @@ function runTest(testNum) {
"---", null,
"context-savevideo", true,
"context-video-saveimage", true,
"context-sendvideo", true
"context-sendvideo", true,
"context-castvideo", null,
[], null
].concat(inspectItems));
closeContextMenu();
openContextMenuFor(audio_in_video); // Invoke context menu for next test.
@ -218,7 +220,9 @@ function runTest(testNum) {
"---", null,
"context-savevideo", true,
"context-video-saveimage", false,
"context-sendvideo", true
"context-sendvideo", true,
"context-castvideo", null,
[], null
].concat(inspectItems));
closeContextMenu();
openContextMenuFor(video_bad2); // Invoke context menu for next test.
@ -242,7 +246,9 @@ function runTest(testNum) {
"---", null,
"context-savevideo", false,
"context-video-saveimage", false,
"context-sendvideo", false
"context-sendvideo", false,
"context-castvideo", null,
[], null
].concat(inspectItems));
closeContextMenu();
openContextMenuFor(iframe); // Invoke context menu for next test.
@ -301,6 +307,8 @@ function runTest(testNum) {
"context-savevideo", true,
"context-video-saveimage", true,
"context-sendvideo", true,
"context-castvideo", null,
[], null,
"frame", null,
["context-showonlythisframe", true,
"context-openframeintab", true,

View File

@ -924,7 +924,7 @@ const CustomizableWidgets = [
id: "loop-call-button",
type: "custom",
label: "loop-call-button3.label",
tooltiptext: "loop-call-button2.tooltiptext",
tooltiptext: "loop-call-button3.tooltiptext",
defaultArea: CustomizableUI.AREA_NAVBAR,
introducedInVersion: 1,
onBuild: function(aDocument) {

View File

@ -99,6 +99,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerParent",
"resource://gre/modules/LoginManagerParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
"resource://gre/modules/SimpleServiceDiscovery.jsm");
#ifdef NIGHTLY_BUILD
XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX",
"resource:///modules/SignInToWebsite.jsm");
@ -747,8 +750,30 @@ BrowserGlue.prototype = {
FormValidationHandler.uninit();
},
_initServiceDiscovery: function () {
var rokuDevice = {
id: "roku:ecp",
target: "roku:ecp",
factory: function(aService) {
Cu.import("resource://gre/modules/RokuApp.jsm");
return new RokuApp(aService);
},
mirror: false,
types: ["video/mp4"],
extensions: ["mp4"]
};
// Register targets
SimpleServiceDiscovery.registerDevice(rokuDevice);
// Search for devices continuously every 120 seconds
SimpleServiceDiscovery.search(120 * 1000);
},
// All initial windows have opened.
_onWindowsRestored: function BG__onWindowsRestored() {
this._initServiceDiscovery();
// Show update notification, if needed.
if (Services.prefs.prefHasUserValue("app.update.postupdate"))
this._showUpdateNotification();
@ -1344,7 +1369,7 @@ BrowserGlue.prototype = {
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 23;
const UI_VERSION = 24;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0;
try {
@ -1422,15 +1447,6 @@ BrowserGlue.prototype = {
}
}
if (currentUIVersion < 8) {
// Reset homepage pref for users who have it set to google.com/firefox
let uri = Services.prefs.getComplexValue("browser.startup.homepage",
Ci.nsIPrefLocalizedString).data;
if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) {
Services.prefs.clearUserPref("browser.startup.homepage");
}
}
if (currentUIVersion < 9) {
// This code adds the customizable downloads buttons.
let currentset = xulStore.getValue(BROWSER_DOCURL, "nav-bar", "currentset");
@ -1495,10 +1511,6 @@ BrowserGlue.prototype = {
}
}
if (currentUIVersion < 13) {
/* Obsolete */
}
if (currentUIVersion < 14) {
// DOM Storage doesn't specially handle about: pages anymore.
let path = OS.Path.join(OS.Constants.Path.profileDir,
@ -1593,6 +1605,18 @@ BrowserGlue.prototype = {
}
}
if (currentUIVersion < 24) {
// Reset homepage pref for users who have it set to start.mozilla.org
// or google.com/firefox.
const HOMEPAGE_PREF = "browser.startup.homepage";
let uri = Services.prefs.getComplexValue(HOMEPAGE_PREF,
Ci.nsIPrefLocalizedString).data;
if (uri && (uri.startsWith("http://start.mozilla.org") ||
/^https?:\/\/(www\.)?google\.[a-z.]+\/firefox/i.test(uri))) {
Services.prefs.clearUserPref(HOMEPAGE_PREF);
}
}
// Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},

View File

@ -313,6 +313,11 @@ nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes,
}
}
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs) {
(void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
}
return NS_OK;
}
@ -326,29 +331,25 @@ nsGNOMEShellService::GetShouldCheckDefaultBrowser(bool* aResult)
return NS_OK;
}
nsCOMPtr<nsIPrefBranch> prefs;
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (pserve)
pserve->GetBranch("", getter_AddRefs(prefs));
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return rv;
}
if (prefs)
prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
return NS_OK;
return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
}
NS_IMETHODIMP
nsGNOMEShellService::SetShouldCheckDefaultBrowser(bool aShouldCheck)
{
nsCOMPtr<nsIPrefBranch> prefs;
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (pserve)
pserve->GetBranch("", getter_AddRefs(prefs));
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return rv;
}
if (prefs)
prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
return NS_OK;
return prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
}
NS_IMETHODIMP

View File

@ -90,7 +90,12 @@ nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
return NS_ERROR_FAILURE;
}
}
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs) {
(void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
}
return NS_OK;
}
@ -104,27 +109,25 @@ nsMacShellService::GetShouldCheckDefaultBrowser(bool* aResult)
return NS_OK;
}
nsCOMPtr<nsIPrefBranch> prefs;
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (pserve)
pserve->GetBranch("", getter_AddRefs(prefs));
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return rv;
}
prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
return NS_OK;
return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
}
NS_IMETHODIMP
nsMacShellService::SetShouldCheckDefaultBrowser(bool aShouldCheck)
{
nsCOMPtr<nsIPrefBranch> prefs;
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (pserve)
pserve->GetBranch("", getter_AddRefs(prefs));
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return rv;
}
prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
return NS_OK;
return prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
}
NS_IMETHODIMP

View File

@ -275,20 +275,15 @@ nsWindowsShellService::ShortcutMaintenance()
return NS_ERROR_UNEXPECTED;
NS_NAMED_LITERAL_CSTRING(prefName, "browser.taskbar.lastgroupid");
nsCOMPtr<nsIPrefService> prefs =
nsCOMPtr<nsIPrefBranch> prefs =
do_GetService(NS_PREFSERVICE_CONTRACTID);
if (!prefs)
return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIPrefBranch> prefBranch;
prefs->GetBranch(nullptr, getter_AddRefs(prefBranch));
if (!prefBranch)
return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsISupportsString> prefString;
rv = prefBranch->GetComplexValue(prefName.get(),
NS_GET_IID(nsISupportsString),
getter_AddRefs(prefString));
rv = prefs->GetComplexValue(prefName.get(),
NS_GET_IID(nsISupportsString),
getter_AddRefs(prefString));
if (NS_SUCCEEDED(rv)) {
nsAutoString version;
prefString->GetData(version);
@ -304,9 +299,9 @@ nsWindowsShellService::ShortcutMaintenance()
return rv;
prefString->SetData(appId);
rv = prefBranch->SetComplexValue(prefName.get(),
NS_GET_IID(nsISupportsString),
prefString);
rv = prefs->SetComplexValue(prefName.get(),
NS_GET_IID(nsISupportsString),
prefString);
if (NS_FAILED(rv)) {
NS_WARNING("Couldn't set last user model id!");
return NS_ERROR_UNEXPECTED;
@ -707,6 +702,11 @@ nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
}
}
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
if (prefs) {
(void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
}
return rv;
}
@ -722,13 +722,11 @@ nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult)
return NS_OK;
}
nsCOMPtr<nsIPrefBranch> prefs;
nsresult rv;
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = pserve->GetBranch("", getter_AddRefs(prefs));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return rv;
}
return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
}
@ -736,14 +734,11 @@ nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult)
NS_IMETHODIMP
nsWindowsShellService::SetShouldCheckDefaultBrowser(bool aShouldCheck)
{
nsCOMPtr<nsIPrefBranch> prefs;
nsresult rv;
nsCOMPtr<nsIPrefService> pserve(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = pserve->GetBranch("", getter_AddRefs(prefs));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) {
return rv;
}
return prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, aShouldCheck);
}

View File

@ -1383,15 +1383,9 @@ TabCanvas.prototype = Utils.extend(new Subscribable(), {
if (!w || !h)
return;
if (!this.tab.linkedBrowser.contentWindow) {
Utils.log('no tab.linkedBrowser.contentWindow in TabCanvas.paint()');
return;
}
let win = this.tab.linkedBrowser.contentWindow;
gPageThumbnails.captureToCanvas(win, this.canvas);
this._sendToSubscribers("painted");
gPageThumbnails.captureToCanvas(this.tab.linkedBrowser, this.canvas, () => {
this._sendToSubscribers("painted");
});
},
// ----------

View File

@ -29,7 +29,7 @@ let test = Task.async(function*() {
let selection = TimelineView.overview.getSelection();
is((selection.start) | 0,
(markers[0].start * TimelineView.overview.dataScaleX) | 0,
((markers[0].start - markers.startTime) * TimelineView.overview.dataScaleX) | 0,
"The initial selection start is correct.");
is((selection.end - selection.start) | 0,

View File

@ -107,13 +107,19 @@ let TimelineController = {
* Starts the recording, updating the UI as needed.
*/
_startRecording: function*() {
this._markers = [];
this._markers.startTime = performance.now();
this._markers.endTime = performance.now();
this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
TimelineView.handleRecordingStarted();
yield gFront.start();
let startTime = yield gFront.start();
// Times must come from the actor in order to be self-consistent.
// However, we also want to update the view with the elapsed time
// even when the actor is not generating data. To do this we get
// the local time and use it to compute a reasonable elapsed time.
// See _onRecordingTick.
this._localStartTime = performance.now();
this._markers = [];
this._markers.startTime = startTime;
this._markers.endTime = startTime;
this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
},
/**
@ -142,9 +148,12 @@ let TimelineController = {
* @param array markers
* A list of new markers collected since the last time this
* function was invoked.
* @param number endTime
* A time after the last marker in markers was collected.
*/
_onMarkers: function(markers) {
_onMarkers: function(markers, endTime) {
Array.prototype.push.apply(this._markers, markers);
this._markers.endTime = endTime;
},
/**
@ -152,7 +161,13 @@ let TimelineController = {
* Updates the markers store with the current time and the timeline overview.
*/
_onRecordingTick: function() {
this._markers.endTime = performance.now();
// Compute an approximate ending time for the view. This is
// needed to ensure that the view updates even when new data is
// not being generated.
let fakeTime = this._markers.startTime + (performance.now() - this._localStartTime);
if (fakeTime > this._markers.endTime) {
this._markers.endTime = fakeTime;
}
TimelineView.handleMarkersUpdate(this._markers);
}
};
@ -214,12 +229,12 @@ let TimelineView = {
let markers = TimelineController.getMarkers();
if (markers.length) {
let start = markers[0].start * this.overview.dataScaleX;
let start = (markers[0].start - markers.startTime) * this.overview.dataScaleX;
let end = start + this.overview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
this.overview.setSelection({ start, end });
} else {
let duration = markers.endTime - markers.startTime;
this.waterfall.setData(markers, 0, duration);
this.waterfall.setData(markers, markers.startTime, markers.endTime);
}
window.emit(EVENTS.RECORDING_ENDED);
@ -251,8 +266,8 @@ let TimelineView = {
let end = selection.end / this.overview.dataScaleX;
let markers = TimelineController.getMarkers();
let timeStart = Math.min(start, end);
let timeEnd = Math.max(start, end);
let timeStart = markers.startTime + Math.min(start, end);
let timeEnd = markers.startTime + Math.max(start, end);
this.waterfall.setData(markers, timeStart, timeEnd);
},

View File

@ -170,6 +170,9 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
ctx.beginPath();
for (let { start, end } of batch) {
start -= this._data.startTime;
end -= this._data.startTime;
let left = start * dataScale;
let duration = Math.max(end - start, OVERVIEW_MARKER_DURATION_MIN);
let width = Math.max(duration * dataScale, this._pixelRatio);

View File

@ -76,16 +76,17 @@ Waterfall.prototype = {
* @param array markers
* A list of markers received from the controller.
* @param number timeStart
* The delta time (in milliseconds) to start drawing from.
* The time (in milliseconds) to start drawing from.
* @param number timeEnd
* The delta time (in milliseconds) to end drawing at.
* The time (in milliseconds) to end drawing at.
*/
setData: function(markers, timeStart, timeEnd) {
this.clearView();
let dataScale = this._waterfallWidth / (timeEnd - timeStart);
this._drawWaterfallBackground(dataScale);
this._buildHeader(this._headerContents, timeStart, dataScale);
// Label the header as if the first possible marker was at T=0.
this._buildHeader(this._headerContents, timeStart - markers.startTime, dataScale);
this._buildMarkers(this._listContents, markers, timeStart, timeEnd, dataScale);
},

View File

@ -23,6 +23,7 @@ support-files =
[browser_audionode-actor-is-source.js]
[browser_audionode-actor-bypass.js]
[browser_audionode-actor-connectnode-disconnect.js]
[browser_audionode-actor-connectparam.js]
[browser_webaudio-actor-simple.js]
[browser_webaudio-actor-destroy-node.js]
[browser_webaudio-actor-connect-param.js]

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that AudioNodeActor#connectParam() work.
* Uses the editor front as the actors do not retain connect state.
*/
function spawnTest() {
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
let { panelWin } = panel;
let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
reload(target);
let [actors] = yield Promise.all([
get3(gFront, "create-node"),
waitForGraphRendered(panelWin, 3, 2)
]);
let [dest, osc, gain] = actors;
yield osc.disconnect();
osc.connectParam(gain, "gain");
yield Promise.all([
waitForGraphRendered(panelWin, 3, 1, 1),
once(gAudioNodes, "connect")
]);
ok(true, "Oscillator connect to Gain's Gain AudioParam, event emitted.");
yield teardown(panel);
finish();
}

View File

@ -30,13 +30,14 @@ function CloseUI() {
}
function BuildUI(addons) {
BuildItem(addons.adb, true /* is adb */);
BuildItem(addons.adb, "adb");
BuildItem(addons.adapters, "adapters");
for (let addon of addons.simulators) {
BuildItem(addon, false /* is adb */);
BuildItem(addon, "simulator");
}
}
function BuildItem(addon, isADB) {
function BuildItem(addon, type) {
function onAddonUpdate(event, arg) {
switch (event) {
@ -73,20 +74,29 @@ function BuildItem(addon, isADB) {
let li = document.createElement("li");
li.setAttribute("status", addon.status);
// Used in tests
if (isADB) {
li.setAttribute("addon", "adb");
} else {
li.setAttribute("addon", "simulator-" + addon.version);
}
let name = document.createElement("span");
name.className = "name";
if (isADB) {
name.textContent = Strings.GetStringFromName("addons_adb_label");
} else {
let stability = Strings.GetStringFromName("addons_" + addon.stability);
name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
switch (type) {
case "adb":
li.setAttribute("addon", type);
name.textContent = Strings.GetStringFromName("addons_adb_label");
break;
case "adapters":
li.setAttribute("addon", type);
try {
name.textContent = Strings.GetStringFromName("addons_adapters_label");
} catch(e) {
// This code (bug 1081093) will be backported to Aurora, which doesn't
// contain this string.
name.textContent = "Tools Adapters Add-on";
}
break;
case "simulator":
li.setAttribute("addon", "simulator-" + addon.version);
let stability = Strings.GetStringFromName("addons_" + addon.stability);
name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
break;
}
li.appendChild(name);
@ -111,7 +121,7 @@ function BuildItem(addon, isADB) {
let progress = document.createElement("progress");
li.appendChild(progress);
if (isADB) {
if (type == "adb") {
let warning = document.createElement("p");
warning.textContent = Strings.GetStringFromName("addons_adb_warning");
warning.className = "warning";

View File

@ -69,15 +69,22 @@ let UI = {
this.autoSelectProject();
});
// Auto install the ADB Addon Helper. Only once.
// If the user decides to uninstall the addon, we won't install it again.
let autoInstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
if (autoInstallADBHelper && !Devices.helperAddonInstalled) {
// Auto install the ADB Addon Helper and Tools Adapters. Only once.
// If the user decides to uninstall any of this addon, we won't install it again.
let autoinstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
let autoinstallFxdtAdapters = Services.prefs.getBoolPref("devtools.webide.autoinstallFxdtAdapters");
if (autoinstallADBHelper) {
GetAvailableAddons().then(addons => {
addons.adb.install();
}, console.error);
}
if (autoinstallFxdtAdapters) {
GetAvailableAddons().then(addons => {
addons.adapters.install();
}, console.error);
}
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", false);
this.lastConnectedRuntime = Services.prefs.getCharPref("devtools.webide.lastConnectedRuntime");

View File

@ -13,8 +13,10 @@ const {GetAddonsJSON} = require("devtools/webide/remote-resources");
let SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
let ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
let ADAPTERS_LINK = Services.prefs.getCharPref("devtools.webide.adaptersAddonURL");
let SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
let ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
let ADAPTERS_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adaptersAddonID");
let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
let OS = "";
@ -26,36 +28,24 @@ if (platform.indexOf("Win") != -1) {
if (platform.indexOf("x86_64") != -1) {
OS = "linux64";
} else {
OS = "linux";
OS = "linux32";
}
}
Simulator.on("unregister", updateSimulatorAddons);
Simulator.on("register", updateSimulatorAddons);
Devices.on("addon-status-updated", updateAdbAddon);
function updateSimulatorAddons(event, version) {
let addonsListener = {};
addonsListener.onEnabled =
addonsListener.onDisabled =
addonsListener.onInstalled =
addonsListener.onUninstalled = (updatedAddon) => {
GetAvailableAddons().then(addons => {
let foundAddon = null;
for (let addon of addons.simulators) {
if (addon.version == version) {
foundAddon = addon;
break;
for (let a of [...addons.simulators, addons.adb, addons.adapters]) {
if (a.addonID == updatedAddon.id) {
a.updateInstallStatus();
}
}
if (!foundAddon) {
console.warn("An unknown simulator (un)registered", version);
return;
}
foundAddon.updateInstallStatus();
});
}
function updateAdbAddon() {
GetAvailableAddons().then(addons => {
addons.adb.updateInstallStatus();
});
}
AddonManager.addAddonListener(addonsListener);
let GetAvailableAddons_promise = null;
let GetAvailableAddons = exports.GetAvailableAddons = function() {
@ -73,6 +63,7 @@ let GetAvailableAddons = exports.GetAvailableAddons = function() {
}
}
addons.adb = new ADBAddon();
addons.adapters = new AdaptersAddon();
deferred.resolve(addons);
}, e => {
GetAvailableAddons_promise = null;
@ -99,13 +90,23 @@ Addon.prototype = {
return this._status;
},
install: function() {
if (this.status != "uninstalled") {
throw new Error("Not uninstalled");
}
this.status = "preparing";
updateInstallStatus: function() {
AddonManager.getAddonByID(this.addonID, (addon) => {
if (addon && !addon.userDisabled) {
this.status = "installed";
} else {
this.status = "uninstalled";
}
});
},
install: function() {
AddonManager.getAddonByID(this.addonID, (addon) => {
if (addon && !addon.userDisabled) {
this.status = "installed";
return;
}
this.status = "preparing";
if (addon && addon.userDisabled) {
addon.userDisabled = false;
} else {
@ -115,7 +116,6 @@ Addon.prototype = {
}, "application/x-xpinstall");
}
});
},
uninstall: function() {
@ -167,44 +167,30 @@ function SimulatorAddon(stability, version) {
EventEmitter.decorate(this);
this.stability = stability;
this.version = version;
this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, OS)
// This addon uses the string "linux" for "linux32"
let fixedOS = OS == "linux32" ? "linux" : OS;
this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, fixedOS)
.replace(/#VERSION#/g, version)
.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
this.addonID = SIMULATOR_ADDON_ID.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
this.updateInstallStatus();
}
SimulatorAddon.prototype = Object.create(Addon.prototype, {
updateInstallStatus: {
enumerable: true,
value: function() {
let sim = Simulator.getByVersion(this.version);
if (sim) {
this.status = "installed";
} else {
this.status = "uninstalled";
}
}
},
});
SimulatorAddon.prototype = Object.create(Addon.prototype);
function ADBAddon() {
EventEmitter.decorate(this);
this.xpiLink = ADB_LINK.replace(/#OS#/g, OS);
// This addon uses the string "linux" for "linux32"
let fixedOS = OS == "linux32" ? "linux" : OS;
this.xpiLink = ADB_LINK.replace(/#OS#/g, fixedOS);
this.addonID = ADB_ADDON_ID;
this.updateInstallStatus();
}
ADBAddon.prototype = Object.create(Addon.prototype);
ADBAddon.prototype = Object.create(Addon.prototype, {
updateInstallStatus: {
enumerable: true,
value: function() {
if (Devices.helperAddonInstalled) {
this.status = "installed";
} else {
this.status = "uninstalled";
}
}
},
});
function AdaptersAddon() {
EventEmitter.decorate(this);
this.xpiLink = ADAPTERS_LINK.replace(/#OS#/g, OS);
this.addonID = ADAPTERS_ADDON_ID;
this.updateInstallStatus();
}
AdaptersAddon.prototype = Object.create(Addon.prototype);

View File

@ -20,6 +20,10 @@ support-files =
addons/adbhelper-linux64.xpi
addons/adbhelper-win32.xpi
addons/adbhelper-mac64.xpi
addons/fxdt-adapters-linux32.xpi
addons/fxdt-adapters-linux64.xpi
addons/fxdt-adapters-win32.xpi
addons/fxdt-adapters-mac64.xpi
head.js
hosted_app.manifest
templates.json

View File

@ -27,25 +27,22 @@ Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
Services.prefs.setCharPref("devtools.webide.addonsURL", TEST_BASE + "addons/simulators.json");
Services.prefs.setCharPref("devtools.webide.simulatorAddonsURL", TEST_BASE + "addons/fxos_#SLASHED_VERSION#_simulator-#OS#.xpi");
Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
Services.prefs.setCharPref("devtools.webide.adaptersAddonURL", TEST_BASE + "addons/fxdt-adapters-#OS#.xpi");
Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.webide.templatesURL");
Services.prefs.clearUserPref("devtools.webide.enabled");
Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
Services.prefs.clearUserPref("devtools.webide.addonsURL");
Services.prefs.clearUserPref("devtools.webide.simulatorAddonsURL");
Services.prefs.clearUserPref("devtools.webide.adbAddonURL");
Services.prefs.clearUserPref("devtools.webide.autoInstallADBHelper", false);
Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
Services.prefs.clearUserPref("devtools.webide.autoinstallFxdtAdapters");
});
function openWebIDE(autoInstallADBHelper) {
function openWebIDE(autoInstallAddons) {
info("opening WebIDE");
if (!autoInstallADBHelper) {
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
}
Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", !!autoInstallAddons);
Services.prefs.setBoolPref("devtools.webide.autoinstallFxdtAdapters", !!autoInstallAddons);
let deferred = promise.defer();

View File

@ -109,10 +109,10 @@
let lis;
lis = addonDoc.querySelectorAll("li");
is(lis.length, 4, "4 addons listed");
is(lis.length, 5, "5 addons listed");
lis = addonDoc.querySelectorAll('li[status="installed"]');
is(lis.length, 2, "2 addons installed");
is(lis.length, 3, "3 addons installed");
lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
is(lis.length, 2, "2 addons uninstalled");

View File

@ -6,6 +6,7 @@
pref("devtools.webide.showProjectEditor", true);
pref("devtools.webide.templatesURL", "https://code.cdn.mozilla.net/templates/list.json");
pref("devtools.webide.autoinstallADBHelper", true);
pref("devtools.webide.autoinstallFxdtAdapters", false);
pref("devtools.webide.restoreLastProject", true);
pref("devtools.webide.enableLocalRuntime", true);
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
@ -13,6 +14,8 @@ pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
pref("devtools.webide.lastConnectedRuntime", "");
pref("devtools.webide.lastSelectedProject", "");

View File

@ -127,7 +127,7 @@ function test() {
is(Application.prefs.root, "", "Check the Application preference root");
// test for user changed preferences
ok(Application.prefs.get("browser.shell.checkDefaultBrowser").modified, "A single preference is marked as modified.");
ok(Application.prefs.get("browser.dom.window.dump.enabled").modified, "A single preference is marked as modified.");
ok(!Application.prefs.get(testdata.string).modified, "A single preference is marked as not modified.");
// test for a locked preference

View File

@ -484,6 +484,8 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY emailImageCmd.accesskey "g">
<!ENTITY emailVideoCmd.label "Email Video…">
<!ENTITY emailVideoCmd.accesskey "a">
<!ENTITY castVideoCmd.label "Send Video To Device">
<!ENTITY castVideoCmd.accesskey "e">
<!ENTITY emailAudioCmd.label "Email Audio…">
<!ENTITY emailAudioCmd.accesskey "a">
<!ENTITY playPluginCmd.label "Activate this plugin">

View File

@ -100,7 +100,7 @@ quit-button.tooltiptext.mac = Quit %1$S (%2$S)
# LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
# approval before you change it.
loop-call-button3.label = Hello
loop-call-button2.tooltiptext = Start a conversation
loop-call-button3.tooltiptext = Start a conversation
social-share-button.label = Share This Page
social-share-button.tooltiptext = Share This Page

View File

@ -52,6 +52,7 @@ addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
addons_install_button=install
addons_uninstall_button=uninstall
addons_adb_label=ADB Helper Add-on
addons_adapters_label=Tools Adapters Add-on
addons_adb_warning=USB devices won't be detected without this add-on
addons_status_unknown=?
addons_status_installed=Installed

View File

@ -0,0 +1,160 @@
// -*- Mode: js; 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";
this.EXPORTED_SYMBOLS = ["CastingApps"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm");
var CastingApps = {
_sendEventToVideo: function (element, data) {
let event = element.ownerDocument.createEvent("CustomEvent");
event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(data));
element.dispatchEvent(event);
},
makeURI: function (url, charset, baseURI) {
return Services.io.newURI(url, charset, baseURI);
},
getVideo: function (element) {
if (!element) {
return null;
}
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
// Grab the poster attribute from the <video>
let posterURL = element.poster;
// First, look to see if the <video> has a src attribute
let sourceURL = element.src;
// If empty, try the currentSrc
if (!sourceURL) {
sourceURL = element.currentSrc;
}
if (sourceURL) {
// Use the file extension to guess the mime type
let sourceURI = this.makeURI(sourceURL, null, this.makeURI(element.baseURI));
if (this.allowableExtension(sourceURI, extensions)) {
return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
}
}
// Next, look to see if there is a <source> child element that meets
// our needs
let sourceNodes = element.getElementsByTagName("source");
for (let sourceNode of sourceNodes) {
let sourceURI = this.makeURI(sourceNode.src, null, this.makeURI(sourceNode.baseURI));
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
// fallback to using the file extension to guess the mime type
if (this.allowableMimeType(sourceNode.type, types) || this.allowableExtension(sourceURI, extensions)) {
return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
}
}
return null;
},
sendVideoToService: function (videoElement, service) {
if (!service)
return;
let video = this.getVideo(videoElement);
if (!video) {
return;
}
// Make sure we have a player app for the given service
let app = SimpleServiceDiscovery.findAppForService(service);
if (!app)
return;
video.title = videoElement.ownerDocument.defaultView.top.document.title;
if (video.element) {
// If the video is currently playing on the device, pause it
if (!video.element.paused) {
video.element.pause();
}
}
app.stop(() => {
app.start(started => {
if (!started) {
Cu.reportError("CastingApps: Unable to start app");
return;
}
app.remoteMedia(remoteMedia => {
if (!remoteMedia) {
Cu.reportError("CastingApps: Failed to create remotemedia");
return;
}
this.session = {
service: service,
app: app,
remoteMedia: remoteMedia,
data: {
title: video.title,
source: video.source,
poster: video.poster
},
videoRef: Cu.getWeakReference(video.element)
};
}, this);
});
});
},
getServicesForVideo: function (videoElement) {
let video = this.getVideo(videoElement);
if (!video) {
return {};
}
let filteredServices = SimpleServiceDiscovery.services.filter(service => {
return this.allowableExtension(video.sourceURI, service.extensions) ||
this.allowableMimeType(video.type, service.types);
});
return filteredServices;
},
// RemoteMedia callback API methods
onRemoteMediaStart: function (remoteMedia) {
if (!this.session) {
return;
}
remoteMedia.load(this.session.data);
let video = this.session.videoRef.get();
if (video) {
this._sendEventToVideo(video, { active: true });
}
},
onRemoteMediaStop: function (remoteMedia) {
},
onRemoteMediaStatus: function (remoteMedia) {
},
allowableExtension: function (uri, extensions) {
return (uri instanceof Ci.nsIURL) && extensions.indexOf(uri.fileExtension) != -1;
},
allowableMimeType: function (type, types) {
return types.indexOf(type) != -1;
}
};

View File

@ -14,6 +14,7 @@ XPCSHELL_TESTS_MANIFESTS += [
EXTRA_JS_MODULES += [
'BrowserNewTabPreloader.jsm',
'BrowserUITelemetry.jsm',
'CastingApps.jsm',
'Chat.jsm',
'ContentClick.jsm',
'ContentLinkHandler.jsm',

View File

@ -165,9 +165,11 @@
}
/* Vertical toolbar border */
#main-window[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after,
#main-window[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
#main-window[sizemode=normal] #navigator-toolbox:-moz-lwtheme {
#main-window:not([customizing])[sizemode=normal] #navigator-toolbox:not(:-moz-lwtheme)::after,
#main-window:not([customizing])[sizemode=normal] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar):not(:-moz-lwtheme),
#main-window:not([customizing])[sizemode=normal] #navigator-toolbox:-moz-lwtheme,
#main-window[customizing] #navigator-toolbox::after,
#main-window[customizing] #navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
border-left: 1px solid @toolbarShadowColor@;
border-right: 1px solid @toolbarShadowColor@;
background-clip: padding-box;

View File

@ -2834,10 +2834,10 @@ nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
if (currentValue != aValue) {
if (aValue) {
++gProfileTimelineRecordingsCount;
mProfileTimelineStartTime = TimeStamp::Now();
mProfileTimelineRecording = true;
} else {
--gProfileTimelineRecordingsCount;
mProfileTimelineStartTime = TimeStamp();
mProfileTimelineRecording = false;
ClearProfileTimelineMarkers();
}
}
@ -2851,7 +2851,7 @@ nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
NS_IMETHODIMP
nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue)
{
*aValue = !mProfileTimelineStartTime.IsNull();
*aValue = mProfileTimelineRecording;
return NS_OK;
}
@ -2939,10 +2939,12 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
#endif
}
float
nsDocShell::GetProfileTimelineDelta()
nsresult
nsDocShell::Now(DOMHighResTimeStamp* aWhen)
{
return (TimeStamp::Now() - mProfileTimelineStartTime).ToMilliseconds();
bool ignore;
*aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds();
return NS_OK;
}
void
@ -2950,8 +2952,9 @@ nsDocShell::AddProfileTimelineMarker(const char* aName,
TracingMetadata aMetaData)
{
#ifdef MOZ_ENABLE_PROFILER_SPS
if (!mProfileTimelineStartTime.IsNull()) {
float delta = GetProfileTimelineDelta();
if (mProfileTimelineRecording) {
DOMHighResTimeStamp delta;
Now(&delta);
ProfilerMarkerTracing* payload = new ProfilerMarkerTracing("Timeline",
aMetaData);
mProfileTimelineMarkers.AppendElement(
@ -2966,8 +2969,9 @@ nsDocShell::AddProfileTimelineMarker(const char* aName,
TracingMetadata aMetaData)
{
#ifdef MOZ_ENABLE_PROFILER_SPS
if (!mProfileTimelineStartTime.IsNull()) {
float delta = GetProfileTimelineDelta();
if (mProfileTimelineRecording) {
DOMHighResTimeStamp delta;
Now(&delta);
ProfilerMarkerTracing* payload = new ProfilerMarkerTracing("Timeline",
aMetaData,
aCause);

View File

@ -951,15 +951,15 @@ private:
nsWeakPtr mOpener;
nsWeakPtr mOpenedRemote;
// Storing profile timeline markers and if/when recording started
mozilla::TimeStamp mProfileTimelineStartTime;
// True if recording profiles.
bool mProfileTimelineRecording;
#ifdef MOZ_ENABLE_PROFILER_SPS
struct InternalProfileTimelineMarker
{
InternalProfileTimelineMarker(const char* aName,
ProfilerMarkerTracing* aPayload,
float aTime)
DOMHighResTimeStamp aTime)
: mName(aName)
, mPayload(aPayload)
, mTime(aTime)
@ -972,15 +972,11 @@ private:
const char* mName;
ProfilerMarkerTracing* mPayload;
float mTime;
DOMHighResTimeStamp mTime;
};
nsTArray<InternalProfileTimelineMarker*> mProfileTimelineMarkers;
#endif
// Get the elapsed time (in millis) since the profile timeline recording
// started
float GetProfileTimelineDelta();
// Get rid of all the timeline markers accumulated so far
void ClearProfileTimelineMarkers();

View File

@ -54,7 +54,7 @@ interface nsITabParent;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(23157a63-26fd-44a0-a0f9-fdc64dcc004c)]
[scriptable, builtinclass, uuid(da8f78f1-8f20-4d6d-be56-fe53e177b630)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@ -672,6 +672,14 @@ interface nsIDocShell : nsIDocShellTreeItem
*/
[infallible] attribute boolean recordProfileTimelineMarkers;
/**
* Return a DOMHighResTimeStamp representing the number of
* milliseconds from an arbitrary point in time. The reference
* point is shared by all DocShells and is also used by timestamps
* on markers.
*/
DOMHighResTimeStamp now();
/**
* Returns and flushes the profile timeline markers gathered by the docShell
*/

View File

@ -6,6 +6,6 @@
dictionary ProfileTimelineMarker {
DOMString name = "";
DOMTimeStamp start = 0;
DOMTimeStamp end = 0;
DOMHighResTimeStamp start = 0;
DOMHighResTimeStamp end = 0;
};

View File

@ -21,10 +21,10 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.DynamicToolbar.PinReason;
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
import org.mozilla.gecko.ReadingListHelper;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.SuggestedSites;
@ -146,10 +146,6 @@ public class BrowserApp extends GeckoApp
private static final int TABS_ANIMATION_DURATION = 450;
private static final int READER_ADD_SUCCESS = 0;
private static final int READER_ADD_FAILED = 1;
private static final int READER_ADD_DUPLICATE = 2;
private static final String ADD_SHORTCUT_TOAST = "add_shortcut_toast";
public static final String GUEST_BROWSING_ARG = "--guest";
@ -229,6 +225,8 @@ public class BrowserApp extends GeckoApp
private BrowserHealthReporter mBrowserHealthReporter;
private ReadingListHelper mReadingListHelper;
private SystemBarTintManager mTintManager;
// The tab to be selected on editing mode exit.
@ -433,90 +431,6 @@ public class BrowserApp extends GeckoApp
return super.onKeyUp(keyCode, event);
}
void handleReaderListStatusRequest(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final int inReadingList = BrowserDB.isReadingListItem(getContentResolver(), url) ? 1 : 0;
final JSONObject json = new JSONObject();
try {
json.put("url", url);
json.put("inReadingList", inReadingList);
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
return;
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
}
});
}
private void handleReaderAdded(int result, final ContentValues values) {
if (result != READER_ADD_SUCCESS) {
if (result == READER_ADD_FAILED) {
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
} else if (result == READER_ADD_DUPLICATE) {
showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
}
return;
}
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.addReadingListItem(getContentResolver(), values);
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
}
});
}
private ContentValues messageToReadingListContentValues(JSONObject message) {
final ContentValues values = new ContentValues();
values.put(ReadingListItems.URL, message.optString("url"));
values.put(ReadingListItems.TITLE, message.optString("title"));
values.put(ReadingListItems.LENGTH, message.optInt("length"));
values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
return values;
}
void handleReaderRemoved(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.removeReadingListItemWithURL(getContentResolver(), url);
showToast(R.string.page_removed, Toast.LENGTH_SHORT);
}
});
}
private void handleReaderFaviconRequest(final String url) {
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
@Override
public String doInBackground() {
return Favicons.getFaviconURLForPageURL(getContext(), url);
}
@Override
public void onPostExecute(String faviconUrl) {
JSONObject args = new JSONObject();
if (faviconUrl != null) {
try {
args.put("url", url);
args.put("faviconUrl", faviconUrl);
} catch (JSONException e) {
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
}
}
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
}
}).execute();
}
@Override
public void onCreate(Bundle savedInstanceState) {
mAboutHomeStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_ABOUTHOME");
@ -600,8 +514,6 @@ public class BrowserApp extends GeckoApp
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
"Menu:Update",
"Reader:Added",
"Reader:FaviconRequest",
"Search:Keyword",
"Prompt:ShowTop",
"Accounts:Exist");
@ -615,8 +527,6 @@ public class BrowserApp extends GeckoApp
"Feedback:OpenPlayStore",
"Menu:Add",
"Menu:Remove",
"Reader:ListStatusRequest",
"Reader:Removed",
"Reader:Share",
"Settings:Show",
"Telemetry:Gather",
@ -633,6 +543,7 @@ public class BrowserApp extends GeckoApp
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
mOrderedBroadcastHelper = new OrderedBroadcastHelper(appContext);
mBrowserHealthReporter = new BrowserHealthReporter();
mReadingListHelper = new ReadingListHelper(appContext);
if (AppConstants.MOZ_ANDROID_BEAM) {
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
@ -1142,10 +1053,13 @@ public class BrowserApp extends GeckoApp
mBrowserHealthReporter = null;
}
if (mReadingListHelper != null) {
mReadingListHelper.uninit();
mReadingListHelper = null;
}
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
"Menu:Update",
"Reader:Added",
"Reader:FaviconRequest",
"Search:Keyword",
"Prompt:ShowTop",
"Accounts:Exist");
@ -1159,8 +1073,6 @@ public class BrowserApp extends GeckoApp
"Feedback:OpenPlayStore",
"Menu:Add",
"Menu:Remove",
"Reader:ListStatusRequest",
"Reader:Removed",
"Reader:Share",
"Settings:Show",
"Telemetry:Gather",
@ -1519,13 +1431,6 @@ public class BrowserApp extends GeckoApp
}
});
} else if ("Reader:ListStatusRequest".equals(event)) {
handleReaderListStatusRequest(message.getString("url"));
} else if ("Reader:Removed".equals(event)) {
final String url = message.getString("url");
handleReaderRemoved(url);
} else if ("Reader:Share".equals(event)) {
final String title = message.getString("title");
final String url = message.getString("url");
@ -1640,12 +1545,6 @@ public class BrowserApp extends GeckoApp
DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
}
} else if (event.equals("Reader:Added")) {
final int result = message.getInt("result");
handleReaderAdded(result, messageToReadingListContentValues(message));
} else if (event.equals("Reader:FaviconRequest")) {
final String url = message.getString("url");
handleReaderFaviconRequest(url);
} else if (event.equals("Search:Keyword")) {
storeSearchQuery(message.getString("query"));
} else if (event.equals("Prompt:ShowTop")) {

View File

@ -1835,7 +1835,7 @@ public class GeckoAppShell
true, // Media scanner should scan this
mimeType,
f.getAbsolutePath(),
Math.max(0, f.length()),
Math.max(1, f.length()), // Some versions of Android require downloads to be at least length 1
false); // Don't show a notification.
} else {
Context context = getContext();

View File

@ -0,0 +1,195 @@
/* 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;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UIAsyncTask;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
public final class ReadingListHelper implements GeckoEventListener, NativeEventListener {
private static final String LOGTAG = "ReadingListHelper";
private static final int READER_ADD_SUCCESS = 0;
private static final int READER_ADD_FAILED = 1;
private static final int READER_ADD_DUPLICATE = 2;
protected final Context context;
public ReadingListHelper(Context context) {
this.context = context;
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
"Reader:Added", "Reader:FaviconRequest");
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
"Reader:ListStatusRequest", "Reader:Removed");
}
public void uninit() {
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
"Reader:Added", "Reader:FaviconRequest");
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
"Reader:ListStatusRequest", "Reader:Removed");
}
@Override
public void handleMessage(String event, JSONObject message) {
switch(event) {
case "Reader:Added": {
handleReadingListAdded(message);
break;
}
case "Reader:FaviconRequest": {
handleReaderModeFaviconRequest(message.optString("url"));
break;
}
}
}
@Override
public void handleMessage(final String event, final NativeJSObject message,
final EventCallback callback) {
switch(event) {
case "Reader:Removed": {
handleReadingListRemoved(message.getString("url"));
break;
}
case "Reader:ListStatusRequest": {
handleReadingListStatusRequest(message.getString("url"));
break;
}
}
}
/**
* A page can be added to the ReadingList by long-tap of the page-action
* icon, or by tapping the readinglist-add icon in the ReaderMode banner.
*/
private void handleReadingListAdded(JSONObject message) {
final int result = message.optInt("result", READER_ADD_FAILED);
if (result != READER_ADD_SUCCESS) {
if (result == READER_ADD_FAILED) {
showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT);
} else if (result == READER_ADD_DUPLICATE) {
showToast(R.string.reading_list_duplicate, Toast.LENGTH_SHORT);
}
return;
}
final ContentValues values = new ContentValues();
values.put(ReadingListItems.URL, message.optString("url"));
values.put(ReadingListItems.TITLE, message.optString("title"));
values.put(ReadingListItems.LENGTH, message.optInt("length"));
values.put(ReadingListItems.EXCERPT, message.optString("excerpt"));
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.addReadingListItem(context.getContentResolver(), values);
showToast(R.string.reading_list_added, Toast.LENGTH_SHORT);
}
});
}
/**
* Gecko (ReaderMode) requests the page favicon to append to the
* document head for display.
*/
private void handleReaderModeFaviconRequest(final String url) {
(new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
@Override
public String doInBackground() {
return Favicons.getFaviconURLForPageURL(context, url);
}
@Override
public void onPostExecute(String faviconUrl) {
JSONObject args = new JSONObject();
if (faviconUrl != null) {
try {
args.put("url", url);
args.put("faviconUrl", faviconUrl);
} catch (JSONException e) {
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
}
}
GeckoAppShell.sendEventToGecko(
GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
}
}).execute();
}
/**
* A page can be removed from the ReadingList by panel context menu,
* or by tapping the readinglist-remove icon in the ReaderMode banner.
*/
private void handleReadingListRemoved(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
BrowserDB.removeReadingListItemWithURL(context.getContentResolver(), url);
showToast(R.string.page_removed, Toast.LENGTH_SHORT);
}
});
}
/**
* Gecko (ReaderMode) requests the page ReadingList status, to display
* the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
*/
private void handleReadingListStatusRequest(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final int inReadingList =
BrowserDB.isReadingListItem(context.getContentResolver(), url) ? 1 : 0;
final JSONObject json = new JSONObject();
try {
json.put("url", url);
json.put("inReadingList", inReadingList);
} catch (JSONException e) {
Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
return;
}
GeckoAppShell.sendEventToGecko(
GeckoEvent.createBroadcastEvent("Reader:ListStatusReturn", json.toString()));
}
});
}
/**
* Show various status toasts.
*/
private void showToast(final int resId, final int duration) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, resId, duration).show();
}
});
}
}

View File

@ -82,7 +82,7 @@ public class LocalBrowserDB {
private final HashMap<String, Long> mFolderIdMap;
// Use wrapped Boolean so that we can have a null state
private Boolean mDesktopBookmarksExist;
private volatile Boolean mDesktopBookmarksExist;
private final Uri mBookmarksUriWithProfile;
private final Uri mParentsUriWithProfile;
@ -759,12 +759,13 @@ public class LocalBrowserDB {
null);
try {
mDesktopBookmarksExist = c.getCount() > 0;
// Don't read back out of the cache to avoid races with invalidation.
final boolean e = c.getCount() > 0;
mDesktopBookmarksExist = e;
return e;
} finally {
c.close();
}
return mDesktopBookmarksExist;
}
@RobocopTarget

View File

@ -6,13 +6,14 @@ package org.mozilla.gecko.menu;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.widget.ThemedImageButton;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageButton;
public class MenuItemActionBar extends ImageButton
public class MenuItemActionBar extends ThemedImageButton
implements GeckoMenuItem.Layout {
private static final String LOGTAG = "GeckoMenuItemActionBar";

View File

@ -368,6 +368,7 @@ gbjar.sources += [
'prompts/PromptService.java',
'prompts/TabInput.java',
'ReaderModeUtils.java',
'ReadingListHelper.java',
'RemoteClientsDialogFragment.java',
'RemoteTabsExpandableListAdapter.java',
'Restarter.java',

View File

@ -3,7 +3,40 @@
- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<item gecko:state_private="true"
android:state_pressed="true"
android:state_enabled="true">
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
<shape android:shape="rectangle">
<solid android:color="@color/new_tablet_highlight_pb"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>
</item>
<item gecko:state_private="true"
android:state_focused="true"
android:state_pressed="false">
<inset android:insetTop="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetBottom="@dimen/new_tablet_browser_toolbar_menu_item_inset_vertical"
android:insetLeft="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal"
android:insetRight="@dimen/new_tablet_browser_toolbar_menu_item_inset_horizontal">
<shape android:shape="rectangle">
<solid android:color="@color/new_tablet_highlight_focused_pb"/>
<corners android:radius="@dimen/new_tablet_browser_toolbar_menu_item_corner_radius"/>
</shape>
</inset>
</item>
<item android:state_pressed="true"
android:state_enabled="true">

View File

@ -11,7 +11,7 @@
android:layout_height="match_parent"
android:layout_alignLeft="@+id/back"
android:layout_toLeftOf="@id/menu_items"
android:layout_marginLeft="@dimen/back_button_width_half"
android:layout_marginLeft="@dimen/new_tablet_nav_button_width_half"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:duplicateParentState="true"
@ -25,8 +25,8 @@
<org.mozilla.gecko.toolbar.BackButton android:id="@id/back"
style="@style/UrlBar.ImageButton.NewTablet"
android:layout_width="@dimen/back_button_width"
android:layout_height="@dimen/back_button_width"
android:layout_width="@dimen/new_tablet_nav_button_width"
android:layout_height="@dimen/new_tablet_nav_button_width"
android:layout_centerVertical="true"
android:layout_marginLeft="12dp"
android:layout_alignParentLeft="true"
@ -43,8 +43,7 @@
android:layout_toRightOf="@id/back"
android:layout_toLeftOf="@id/menu_items"/>
<!-- Note: * Values of marginLeft are used to animate the forward button so don't change its value.
* We set the padding on the site security icon to increase its tappable area. -->
<!-- Note: we set the padding on the site security icon to increase its tappable area. -->
<org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
style="@style/UrlBar.Button.Container"
android:layout_toRightOf="@id/back"

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@null"/>

View File

@ -8,10 +8,6 @@
<dimen name="browser_toolbar_height">56dp</dimen>
<dimen name="browser_toolbar_button_padding">16dp</dimen>
<!-- If you update one of these values, update the other. -->
<dimen name="back_button_width">42dp</dimen>
<dimen name="back_button_width_half">21dp</dimen>
<dimen name="tabs_counter_size">26sp</dimen>
<dimen name="panel_grid_view_column_width">200dp</dimen>

View File

@ -25,8 +25,9 @@
<item name="android:layout_marginLeft">@dimen/forward_default_offset</item>
</style>
<!-- Note: this style is for the visible and expanded forward button. We translate/hide
the forward button in code - see BrowserToolbarNewTablet.animateForwardButton. -->
<style name="UrlBar.ImageButton.Forward.NewTablet">
<item name="android:layout_marginLeft">@dimen/new_tablet_forward_default_offset</item>
<item name="android:layout_height">match_parent</item>
<item name="android:paddingTop">0dp</item>
<item name="android:paddingBottom">0dp</item>
@ -34,6 +35,20 @@
<item name="android:layout_marginBottom">11.5dp</item>
<item name="android:src">@drawable/new_tablet_ic_menu_forward</item>
<item name="android:background">@drawable/new_tablet_url_bar_nav_button</item>
<!-- The visible area of the forward button is a nav_button_width and the
non-visible area slides halfway under the back button. This non-visible
area is used to ensure the forward button background fully
covers the space to the right of the back button. -->
<item name="android:layout_width">@dimen/new_tablet_nav_button_width_plus_half</item>
<!-- (See note above) We left align with back,
but only need to hide halfway underneath. -->
<item name="android:layout_marginLeft">@dimen/new_tablet_nav_button_width_half</item>
<!-- We use left padding to center the arrow in the
visible area as opposed to the true width. -->
<item name="android:paddingLeft">18dp</item>
</style>
<style name="UrlBar.ImageButton.TabCount.NewTablet">

View File

@ -17,11 +17,15 @@
<dimen name="browser_toolbar_favicon_size">21.33dip</dimen>
<dimen name="browser_toolbar_shadow_size">2dp</dimen>
<!-- If you update one of these values, update the others. -->
<dimen name="new_tablet_nav_button_width">42dp</dimen>
<dimen name="new_tablet_nav_button_width_half">21dp</dimen>
<dimen name="new_tablet_nav_button_width_plus_half">63dp</dimen>
<dimen name="new_tablet_tab_strip_height">48dp</dimen>
<dimen name="new_tablet_tab_strip_item_width">250dp</dimen>
<dimen name="new_tablet_tab_strip_item_margin">-30dp</dimen>
<dimen name="new_tablet_tab_strip_favicon_size">16dp</dimen>
<dimen name="new_tablet_forward_default_offset">-6dp</dimen>
<dimen name="new_tablet_site_security_height">60dp</dimen>
<dimen name="new_tablet_site_security_width">34dp</dimen>
<!-- We primarily use padding (instead of margins) to increase the hit area. -->

View File

@ -10,7 +10,6 @@ import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
/**
@ -21,15 +20,18 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
private static final int FORWARD_ANIMATION_DURATION = 450;
private final int urlBarViewOffset;
private final int defaultForwardMargin;
private final int forwardButtonTranslationWidth;
public BrowserToolbarNewTablet(final Context context, final AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
urlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
defaultForwardMargin = res.getDimensionPixelSize(R.dimen.new_tablet_forward_default_offset);
forwardButtonTranslationWidth =
getResources().getDimensionPixelOffset(R.dimen.new_tablet_nav_button_width);
// The forward button is initially expanded (in the layout file)
// so translate it for start of the expansion animation; future
// iterations translate it to this position when hiding and will already be set up.
ViewHelper.setTranslationX(forwardButton, -forwardButtonTranslationWidth);
}
@Override
@ -49,25 +51,24 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
@Override
protected void animateForwardButton(final ForwardButtonAnimation animation) {
final boolean showing = (animation == ForwardButtonAnimation.SHOW);
final boolean willShowForward = (animation == ForwardButtonAnimation.SHOW);
// if the forward button's margin is non-zero, this means it has already
// been animated to be visible¸ and vice-versa.
MarginLayoutParams fwdParams = (MarginLayoutParams) forwardButton.getLayoutParams();
if ((fwdParams.leftMargin > defaultForwardMargin && showing) ||
(fwdParams.leftMargin == defaultForwardMargin && !showing)) {
// If we're not in the appropriate state to start a particular animation,
// then we must be in the opposite state and do not need to animate.
final float forwardOffset = ViewHelper.getTranslationX(forwardButton);
if ((forwardOffset >= 0 && willShowForward) ||
forwardOffset < 0 && !willShowForward) {
return;
}
// We want the forward button to show immediately when switching tabs
final PropertyAnimator forwardAnim =
new PropertyAnimator(isSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
final int width = Math.round(forwardButton.getWidth() * .75f);
forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
@Override
public void onPropertyAnimationStart() {
if (!showing) {
if (!willShowForward) {
// Set the margin before the transition when hiding the forward button. We
// have to do this so that the favicon isn't clipped during the transition
MarginLayoutParams layoutParams =
@ -87,26 +88,23 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
@Override
public void onPropertyAnimationEnd() {
if (showing) {
if (willShowForward) {
// Increase the margins to ensure the text does not run outside the View.
MarginLayoutParams layoutParams =
(MarginLayoutParams) urlDisplayLayout.getLayoutParams();
layoutParams.leftMargin = urlBarViewOffset;
layoutParams.leftMargin = forwardButtonTranslationWidth;
layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
layoutParams.leftMargin = urlBarViewOffset;
layoutParams.leftMargin = forwardButtonTranslationWidth;
}
urlDisplayLayout.finishForwardAnimation();
MarginLayoutParams layoutParams = (MarginLayoutParams) forwardButton.getLayoutParams();
layoutParams.leftMargin = defaultForwardMargin + (showing ? width : 0);
ViewHelper.setTranslationX(forwardButton, 0);
requestLayout();
}
});
prepareForwardAnimation(forwardAnim, animation, width);
prepareForwardAnimation(forwardAnim, animation, forwardButtonTranslationWidth);
forwardAnim.start();
}
@ -122,7 +120,7 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
} else {
anim.attach(forwardButton,
PropertyAnimator.Property.TRANSLATION_X,
width);
0);
anim.attach(forwardButton,
PropertyAnimator.Property.ALPHA,
1);

View File

@ -10,6 +10,7 @@ import java.util.Arrays;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.menu.MenuItemActionBar;
import android.content.Context;
import android.graphics.drawable.Drawable;
@ -121,8 +122,13 @@ abstract class BrowserToolbarTabletBase extends BrowserToolbar {
@Override
public void setPrivateMode(final boolean isPrivate) {
super.setPrivateMode(isPrivate);
backButton.setPrivateMode(isPrivate);
forwardButton.setPrivateMode(isPrivate);
for (int i = 0; i < actionItemBar.getChildCount(); ++i) {
final MenuItemActionBar child = (MenuItemActionBar) actionItemBar.getChildAt(i);
child.setPrivateMode(isPrivate);
}
}
protected boolean canDoBack(final Tab tab) {

View File

@ -535,6 +535,8 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
mForwardAnim = anim;
if (animation == ForwardButtonAnimation.HIDE) {
// We animate these items individually, rather than this entire view,
// so that we don't animate certain views, e.g. the stop button.
anim.attach(mTitle,
PropertyAnimator.Property.TRANSLATION_X,
0);

View File

@ -1,4 +1,5 @@
//#filter substitution
//#define VIEW_NAME_SUFFIX EditText
//#define BASE_TYPE android.widget.EditText
//#define STYLE_CONSTRUCTOR 1
//#include ThemedView.java.frag

View File

@ -1,4 +1,5 @@
//#filter substitution
//#define VIEW_NAME_SUFFIX ImageButton
//#define BASE_TYPE android.widget.ImageButton
//#define STYLE_CONSTRUCTOR 1
//#include ThemedView.java.frag

View File

@ -1,4 +1,5 @@
//#filter substitution
//#define VIEW_NAME_SUFFIX ImageView
//#define BASE_TYPE android.widget.ImageView
//#define STYLE_CONSTRUCTOR 1
//#include ThemedView.java.frag

View File

@ -1,4 +1,5 @@
//#filter substitution
//#define VIEW_NAME_SUFFIX RelativeLayout
//#define BASE_TYPE android.widget.RelativeLayout
//#define STYLE_CONSTRUCTOR 1
//#include ThemedView.java.frag

View File

@ -1,4 +1,5 @@
//#filter substitution
//#define VIEW_NAME_SUFFIX TextView
//#define BASE_TYPE android.widget.TextView
//#define STYLE_CONSTRUCTOR 1
//#include ThemedView.java.frag

View File

@ -15,7 +15,7 @@ import android.util.AttributeSet;
public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
implements LightweightTheme.OnChangeListener {
private final LightweightTheme mTheme;
private LightweightTheme mTheme;
private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
private static final int[] STATE_LIGHT = { R.attr.state_light };
@ -32,9 +32,20 @@ public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
//#ifdef STYLE_CONSTRUCTOR
public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context, attrs);
}
//#endif
private void initialize(final Context context, final AttributeSet attrs) {
mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
mAutoUpdateTheme = a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
a.recycle();
}

View File

@ -1,4 +1,5 @@
//#filter substitution
//#define VIEW_NAME_SUFFIX View
//#define BASE_TYPE android.view.View
//#define STYLE_CONSTRUCTOR 1
//#include ThemedView.java.frag

View File

@ -142,6 +142,10 @@ public final class Reporter extends BroadcastReceiver {
Map<String, ScanResult> currentWifiData = mBundle.getWifiData();
for (ScanResult result : results) {
if (currentWifiData.size() > MAX_WIFIS_PER_LOCATION) {
return;
}
String key = result.BSSID;
if (!currentWifiData.containsKey(key)) {
currentWifiData.put(key, result);
@ -156,6 +160,9 @@ public final class Reporter extends BroadcastReceiver {
Map<String, CellInfo> currentCellData = mBundle.getCellData();
for (CellInfo result : cells) {
if (currentCellData.size() > MAX_CELLS_PER_LOCATION) {
return;
}
String key = result.getCellIdentity();
if (!currentCellData.containsKey(key)) {
currentCellData.put(key, result);
@ -191,7 +198,9 @@ public final class Reporter extends BroadcastReceiver {
Log.d(LOG_TAG, "Received bundle: " + mlsObj.toString());
}
AppGlobals.guiLogInfo(mlsObj.toString());
if (wifiCount + cellCount < 1) {
return;
}
try {
DataStorageManager.getInstance().insert(mlsObj.toString(), wifiCount, cellCount);

View File

@ -47,7 +47,8 @@ this.SearchSuggestionController = function SearchSuggestionController(callback =
this.SearchSuggestionController.prototype = {
/**
* The maximum number of local form history results to return.
* The maximum number of local form history results to return. This limit is
* only enforced if remote results are also returned.
*/
maxLocalResults: 7,
@ -200,8 +201,7 @@ this.SearchSuggestionController.prototype = {
return;
}
let fhEntries = [];
let maxHistoryItems = Math.min(result.matchCount, this.maxLocalResults);
for (let i = 0; i < maxHistoryItems; ++i) {
for (let i = 0; i < result.matchCount; ++i) {
fhEntries.push(result.getValueAt(i));
}
deferredFormHistory.resolve({
@ -335,8 +335,13 @@ this.SearchSuggestionController.prototype = {
}
}
// If we have remote results, cap the number of local results
if (results.remote.length) {
results.local = results.local.slice(0, this.maxLocalResults);
}
// We don't want things to appear in both history and suggestions so remove entries from
// remote results that are alrady in local.
// remote results that are already in local.
if (results.remote.length && results.local.length) {
for (let i = 0; i < results.local.length; ++i) {
let term = results.local[i];

View File

@ -25,6 +25,7 @@ SuggestAutoComplete.prototype = {
_init: function() {
this._suggestionController = new SearchSuggestionController(obj => this.onResultsReturned(obj));
this._suggestionController.maxLocalResults = this._historyLimit;
},
get _suggestionLabel() {
@ -57,8 +58,7 @@ SuggestAutoComplete.prototype = {
let finalComments = [];
// If form history has results, add them to the list.
let maxHistoryItems = Math.min(results.local.length, this._historyLimit);
for (let i = 0; i < maxHistoryItems; ++i) {
for (let i = 0; i < results.local.length; ++i) {
finalResults.push(results.local[i]);
finalComments.push("");
}

View File

@ -149,7 +149,6 @@ add_task(function* simple_non_ascii() {
do_check_eq(result.remote[0], "I ❤️ Mozilla");
});
add_task(function* both_local_remote_result_dedupe() {
yield updateSearchHistory("bump", "Mozilla");
@ -269,6 +268,36 @@ add_task(function* both_identical_with_more_than_max_results() {
}
});
add_task(function* noremote_maxLocal() {
let controller = new SearchSuggestionController();
controller.maxLocalResults = 2; // (should be ignored because no remote results)
controller.maxRemoteResults = 0;
let result = yield controller.fetch("letter ", false, getEngine);
do_check_eq(result.term, "letter ");
do_check_eq(result.local.length, 26);
for (let i = 0; i < result.local.length; i++) {
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
}
do_check_eq(result.remote.length, 0);
});
add_task(function* someremote_maxLocal() {
let controller = new SearchSuggestionController();
controller.maxLocalResults = 2;
controller.maxRemoteResults = 2;
let result = yield controller.fetch("letter ", false, getEngine);
do_check_eq(result.term, "letter ");
do_check_eq(result.local.length, 2);
for (let i = 0; i < result.local.length; i++) {
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
}
do_check_eq(result.remote.length, 2);
// "A" and "B" will have been de-duped, start at C for remote results
for (let i = 0; i < result.remote.length; i++) {
do_check_eq(result.remote[i], "letter " + String.fromCharCode("C".charCodeAt() + i));
}
});
add_task(function* one_of_each() {
let controller = new SearchSuggestionController();
controller.maxLocalResults = 1;
@ -288,8 +317,10 @@ add_task(function* local_result_returned_remote_result_disabled() {
controller.maxRemoteResults = 1;
let result = yield controller.fetch("letter ", false, getEngine);
do_check_eq(result.term, "letter ");
do_check_eq(result.local.length, 1);
do_check_eq(result.local[0], "letter A");
do_check_eq(result.local.length, 26);
for (let i = 0; i < 26; i++) {
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
}
do_check_eq(result.remote.length, 0);
Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
});
@ -301,8 +332,10 @@ add_task(function* local_result_returned_remote_result_disabled_after_creation_o
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
let result = yield controller.fetch("letter ", false, getEngine);
do_check_eq(result.term, "letter ");
do_check_eq(result.local.length, 1);
do_check_eq(result.local[0], "letter A");
do_check_eq(result.local.length, 26);
for (let i = 0; i < 26; i++) {
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
}
do_check_eq(result.remote.length, 0);
Services.prefs.setBoolPref("browser.search.suggest.enabled", true);
});
@ -331,8 +364,10 @@ add_task(function* one_local_zero_remote() {
controller.maxRemoteResults = 0;
let result = yield controller.fetch("letter ", false, getEngine);
do_check_eq(result.term, "letter ");
do_check_eq(result.local.length, 1);
do_check_eq(result.local[0], "letter A");
do_check_eq(result.local.length, 26);
for (let i = 0; i < 26; i++) {
do_check_eq(result.local[i], "letter " + String.fromCharCode("A".charCodeAt() + i));
}
do_check_eq(result.remote.length, 0);
});

View File

@ -0,0 +1,105 @@
/* 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/. */
/*
* Common thumbnailing routines used by various consumers, including
* PageThumbs and backgroundPageThumbsContent.
*/
this.EXPORTED_SYMBOLS = ["PageThumbUtils"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Promise.jsm", this);
this.PageThumbUtils = {
// The default background color for page thumbnails.
THUMBNAIL_BG_COLOR: "#fff",
// The namespace for thumbnail canvas elements.
HTML_NAMESPACE: "http://www.w3.org/1999/xhtml",
/**
* Creates a new canvas element in the context of aWindow, or if aWindow
* is undefined, in the context of hiddenDOMWindow.
*
* @param aWindow (optional) The document of this window will be used to
* create the canvas. If not given, the hidden window will be used.
* @return The newly created canvas.
*/
createCanvas: function (aWindow) {
let doc = (aWindow || Services.appShell.hiddenDOMWindow).document;
let canvas = doc.createElementNS(this.HTML_NAMESPACE, "canvas");
canvas.mozOpaque = true;
canvas.mozImageSmoothingEnabled = true;
let [thumbnailWidth, thumbnailHeight] = this.getThumbnailSize();
canvas.width = thumbnailWidth;
canvas.height = thumbnailHeight;
return canvas;
},
/**
* Calculates a preferred initial thumbnail size based on current desktop
* dimensions. The resulting dims will generally be about 1/3 the
* size of the desktop. (jimm: why??)
*
* @return The calculated thumbnail size or a default if unable to calculate.
*/
getThumbnailSize: function () {
if (!this._thumbnailWidth || !this._thumbnailHeight) {
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager);
let left = {}, top = {}, width = {}, height = {};
screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
this._thumbnailWidth = Math.round(width.value / 3);
this._thumbnailHeight = Math.round(height.value / 3);
}
return [this._thumbnailWidth, this._thumbnailHeight];
},
/**
* Determine a good thumbnail crop size and scale for a given content
* window.
*
* @param aWindow The content window.
* @param aCanvas The target canvas.
* @return An array containing width, height and scale.
*/
determineCropSize: function (aWindow, aCanvas) {
if (Cu.isCrossProcessWrapper(aWindow)) {
throw new Error('Do not pass cpows here.');
}
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
// aWindow may be a cpow, add exposed props security values.
let sbWidth = {}, sbHeight = {};
try {
utils.getScrollbarSize(false, sbWidth, sbHeight);
} catch (e) {
// This might fail if the window does not have a presShell.
Cu.reportError("Unable to get scrollbar size in determineCropSize.");
sbWidth.value = sbHeight.value = 0;
}
// Even in RTL mode, scrollbars are always on the right.
// So there's no need to determine a left offset.
let width = aWindow.innerWidth - sbWidth.value;
let height = aWindow.innerHeight - sbHeight.value;
let {width: thumbnailWidth, height: thumbnailHeight} = aCanvas;
let scale = Math.min(Math.max(thumbnailWidth / width, thumbnailHeight / height), 1);
let scaledWidth = width * scale;
let scaledHeight = height * scale;
if (scaledHeight > thumbnailHeight)
height -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);
if (scaledWidth > thumbnailWidth)
width -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);
return [width, height, scale];
}
};

View File

@ -10,13 +10,14 @@ const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
const LATEST_STORAGE_VERSION = 3;
const EXPIRATION_MIN_CHUNK_SIZE = 50;
const EXPIRATION_INTERVAL_SECS = 3600;
var gRemoteThumbId = 0;
// If a request for a thumbnail comes in and we find one that is "stale"
// (or don't find one at all) we automatically queue a request to generate a
// new one.
@ -27,11 +28,6 @@ const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
*/
const THUMBNAIL_DIRECTORY = "thumbnails";
/**
* The default background color for page thumbnails.
*/
const THUMBNAIL_BG_COLOR = "#fff";
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
@ -69,6 +65,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
"resource://gre/modules/PageThumbUtils.jsm");
/**
* Utilities for dealing with promises and Task.jsm
@ -168,72 +166,73 @@ this.PageThumbs = {
},
/**
* Captures a thumbnail for the given window.
* @param aWindow The DOM window to capture a thumbnail from.
* @param aCallback The function to be called when the thumbnail has been
* captured. The first argument will be the data stream
* containing the image data.
*/
capture: function PageThumbs_capture(aWindow, aCallback) {
if (!this._prefEnabled()) {
return;
}
let canvas = this.createCanvas();
this.captureToCanvas(aWindow, canvas);
// Fetch the canvas data on the next event loop tick so that we allow
// some event processing in between drawing to the canvas and encoding
// its data. We want to block the UI as short as possible. See bug 744100.
Services.tm.currentThread.dispatch(function () {
canvas.mozFetchAsStream(aCallback, this.contentType);
}.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
},
/**
* Captures a thumbnail for the given window.
* Asynchronously returns a thumbnail as a blob for the given
* window.
*
* @param aWindow The DOM window to capture a thumbnail from.
* @param aBrowser The <browser> to capture a thumbnail from.
* @return {Promise}
* @resolve {Blob} The thumbnail, as a Blob.
*/
captureToBlob: function PageThumbs_captureToBlob(aWindow) {
captureToBlob: function PageThumbs_captureToBlob(aBrowser) {
if (!this._prefEnabled()) {
return null;
}
let canvas = this.createCanvas();
this.captureToCanvas(aWindow, canvas);
let deferred = Promise.defer();
let type = this.contentType;
// Fetch the canvas data on the next event loop tick so that we allow
// some event processing in between drawing to the canvas and encoding
// its data. We want to block the UI as short as possible. See bug 744100.
canvas.toBlob(function asBlob(blob) {
deferred.resolve(blob, type);
let canvas = this.createCanvas();
this.captureToCanvas(aBrowser, canvas, () => {
canvas.toBlob(blob => {
deferred.resolve(blob, this.contentType);
});
});
return deferred.promise;
},
/**
* Captures a thumbnail from a given window and draws it to the given canvas.
* @param aWindow The DOM window to capture a thumbnail from.
* Note, when dealing with remote content, this api draws into the passed
* canvas asynchronously. Pass aCallback to receive an async callback after
* canvas painting has completed.
* @param aBrowser The browser to capture a thumbnail from.
* @param aCanvas The canvas to draw to.
* @param aCallback (optional) A callback invoked once the thumbnail has been
* rendered to aCanvas.
*/
captureToCanvas: function PageThumbs_captureToCanvas(aWindow, aCanvas) {
captureToCanvas: function PageThumbs_captureToCanvas(aBrowser, aCanvas, aCallback) {
let telemetryCaptureTime = new Date();
this._captureToCanvas(aWindow, aCanvas);
let telemetry = Services.telemetry;
telemetry.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
.add(new Date() - telemetryCaptureTime);
this._captureToCanvas(aBrowser, aCanvas, function () {
Services.telemetry
.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
.add(new Date() - telemetryCaptureTime);
if (aCallback) {
aCallback(aCanvas);
}
});
},
// The background thumbnail service captures to canvas but doesn't want to
// participate in this service's telemetry, which is why this method exists.
_captureToCanvas: function PageThumbs__captureToCanvas(aWindow, aCanvas) {
let [sw, sh, scale] = this._determineCropSize(aWindow, aCanvas);
_captureToCanvas: function (aBrowser, aCanvas, aCallback) {
if (aBrowser.isRemoteBrowser) {
Task.spawn(function () {
let data =
yield this._captureRemoteThumbnail(aBrowser, aCanvas);
let canvas = data.thumbnail;
let ctx = canvas.getContext("2d");
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
aCanvas.getContext("2d").putImageData(imgData, 0, 0);
if (aCallback) {
aCallback(aCanvas);
}
}.bind(this));
return;
}
// Generate in-process content thumbnail
let [width, height, scale] =
PageThumbUtils.determineCropSize(aBrowser.contentWindow, aCanvas);
let ctx = aCanvas.getContext("2d");
// Scale the canvas accordingly.
@ -242,15 +241,78 @@ this.PageThumbs = {
try {
// Draw the window contents to the canvas.
ctx.drawWindow(aWindow, 0, 0, sw, sh, THUMBNAIL_BG_COLOR,
ctx.drawWindow(aBrowser.contentWindow, 0, 0, width, height,
PageThumbUtils.THUMBNAIL_BG_COLOR,
ctx.DRAWWINDOW_DO_NOT_FLUSH);
} catch (e) {
// We couldn't draw to the canvas for some reason.
}
ctx.restore();
if (aCallback) {
aCallback(aCanvas);
}
},
/**
* Asynchrnously render an appropriately scaled thumbnail to canvas.
*
* @param aBrowser The browser to capture a thumbnail from.
* @param aCanvas The canvas to draw to.
* @return a promise
*/
_captureRemoteThumbnail: function (aBrowser, aCanvas) {
let deferred = Promise.defer();
// The index we send with the request so we can identify the
// correct response.
let index = gRemoteThumbId++;
// Thumbnail request response handler
let mm = aBrowser.messageManager;
// Browser:Thumbnail:Response handler
let thumbFunc = function (aMsg) {
// Ignore events unrelated to our request
if (aMsg.data.id != index) {
return;
}
mm.removeMessageListener("Browser:Thumbnail:Response", thumbFunc);
let imageBlob = aMsg.data.thumbnail;
let doc = aBrowser.parentElement.ownerDocument;
let reader = Cc["@mozilla.org/files/filereader;1"].
createInstance(Ci.nsIDOMFileReader);
reader.addEventListener("loadend", function() {
let image = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "img");
image.onload = function () {
let thumbnail = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "canvas");
thumbnail.width = image.naturalWidth;
thumbnail.height = image.naturalHeight;
let ctx = thumbnail.getContext("2d");
ctx.drawImage(image, 0, 0);
deferred.resolve({
thumbnail: thumbnail
});
}
image.src = reader.result;
});
// xxx wish there was a way to skip this encoding step
reader.readAsDataURL(imageBlob);
}
// Send a thumbnail request
mm.addMessageListener("Browser:Thumbnail:Response", thumbFunc);
mm.sendAsyncMessage("Browser:Thumbnail:Request", {
canvasWidth: aCanvas.width,
canvasHeight: aCanvas.height,
background: PageThumbUtils.THUMBNAIL_BG_COLOR,
id: index
});
return deferred.promise;
},
/**
* Captures a thumbnail for the given browser and stores it to the cache.
* @param aBrowser The browser to capture a thumbnail for.
@ -262,19 +324,27 @@ this.PageThumbs = {
}
let url = aBrowser.currentURI.spec;
let channel = aBrowser.docShell.currentDocumentChannel;
let originalURL = channel.originalURI.spec;
let originalURL;
let channelError = false;
// see if this was an error response.
let wasError = this._isChannelErrorResponse(channel);
if (!aBrowser.isRemoteBrowser) {
let channel = aBrowser.docShell.currentDocumentChannel;
originalURL = channel.originalURI.spec;
// see if this was an error response.
channelError = this._isChannelErrorResponse(channel);
} else {
// We need channel info (bug 1073957)
originalURL = url;
}
Task.spawn((function task() {
let isSuccess = true;
try {
let blob = yield this.captureToBlob(aBrowser.contentWindow);
let blob = yield this.captureToBlob(aBrowser);
let buffer = yield TaskUtils.readBlob(blob);
yield this._store(originalURL, url, buffer, wasError);
} catch (_) {
yield this._store(originalURL, url, buffer, channelError);
} catch (ex) {
Components.utils.reportError("Exception thrown during thumbnail capture: '" + ex + "'");
isSuccess = false;
}
if (aCallback) {
@ -374,44 +444,6 @@ this.PageThumbs = {
PageThumbsExpiration.removeFilter(aFilter);
},
/**
* Determines the crop size for a given content window.
* @param aWindow The content window.
* @param aCanvas The target canvas.
* @return An array containing width, height and scale.
*/
_determineCropSize: function PageThumbs_determineCropSize(aWindow, aCanvas) {
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let sbWidth = {}, sbHeight = {};
try {
utils.getScrollbarSize(false, sbWidth, sbHeight);
} catch (e) {
// This might fail if the window does not have a presShell.
Cu.reportError("Unable to get scrollbar size in _determineCropSize.");
sbWidth.value = sbHeight.value = 0;
}
// Even in RTL mode, scrollbars are always on the right.
// So there's no need to determine a left offset.
let sw = aWindow.innerWidth - sbWidth.value;
let sh = aWindow.innerHeight - sbHeight.value;
let {width: thumbnailWidth, height: thumbnailHeight} = aCanvas;
let scale = Math.min(Math.max(thumbnailWidth / sw, thumbnailHeight / sh), 1);
let scaledWidth = sw * scale;
let scaledHeight = sh * scale;
if (scaledHeight > thumbnailHeight)
sh -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);
if (scaledWidth > thumbnailWidth)
sw -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);
return [sw, sh, scale];
},
/**
* Creates a new hidden canvas element.
* @param aWindow The document of this window will be used to create the
@ -419,30 +451,7 @@ this.PageThumbs = {
* @return The newly created canvas.
*/
createCanvas: function PageThumbs_createCanvas(aWindow) {
let doc = (aWindow || Services.appShell.hiddenDOMWindow).document;
let canvas = doc.createElementNS(HTML_NAMESPACE, "canvas");
canvas.mozOpaque = true;
canvas.mozImageSmoothingEnabled = true;
let [thumbnailWidth, thumbnailHeight] = this._getThumbnailSize();
canvas.width = thumbnailWidth;
canvas.height = thumbnailHeight;
return canvas;
},
/**
* Calculates the thumbnail size based on current desktop's dimensions.
* @return The calculated thumbnail size or a default if unable to calculate.
*/
_getThumbnailSize: function PageThumbs_getThumbnailSize() {
if (!this._thumbnailWidth || !this._thumbnailHeight) {
let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager);
let left = {}, top = {}, width = {}, height = {};
screenManager.primaryScreen.GetRectDisplayPix(left, top, width, height);
this._thumbnailWidth = Math.round(width.value / 3);
this._thumbnailHeight = Math.round(height.value / 3);
}
return [this._thumbnailWidth, this._thumbnailHeight];
return PageThumbUtils.createCanvas(aWindow);
},
/**

View File

@ -2,13 +2,11 @@
* 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/. */
(function () { // bug 673569 workaround :(
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.importGlobalProperties(['Blob']);
Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/PageThumbUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -49,7 +47,7 @@ const backgroundPageThumbsContent = {
// in the parent (eg, auth) aren't prevented, but alert() etc are.
// disableDialogs only works on the current inner window, so it has
// to be called every page load, but before scripts run.
if (subj == content.document) {
if (content && subj == content.document) {
content.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).
@ -129,9 +127,19 @@ const backgroundPageThumbsContent = {
capture.finalURL = this._webNav.currentURI.spec;
capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
let canvas = PageThumbs.createCanvas(content);
let canvasDrawDate = new Date();
PageThumbs._captureToCanvas(content, canvas);
let canvas = PageThumbUtils.createCanvas(content);
let [sw, sh, scale] = PageThumbUtils.determineCropSize(content, canvas);
let ctx = canvas.getContext("2d");
ctx.save();
ctx.scale(scale, scale);
ctx.drawWindow(content, 0, 0, sw, sh,
PageThumbUtils.THUMBNAIL_BG_COLOR,
ctx.DRAWWINDOW_DO_NOT_FLUSH);
ctx.restore();
capture.canvasDrawTime = new Date() - canvasDrawDate;
canvas.toBlob(blob => {
@ -184,5 +192,3 @@ const backgroundPageThumbsContent = {
};
backgroundPageThumbsContent.init();
})();

View File

@ -15,6 +15,7 @@ EXTRA_JS_MODULES += [
'BackgroundPageThumbs.jsm',
'PageThumbs.jsm',
'PageThumbsWorker.js',
'PageThumbUtils.jsm',
]
JAR_MANIFESTS += ['jar.mn']

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = e10s # Bug 863512 - thumbnails are disabled with e10s enabled.
support-files =
background_red.html
background_red_redirect.sjs
@ -12,15 +11,16 @@ support-files =
[browser_thumbnails_bg_bad_url.js]
[browser_thumbnails_bg_crash_during_capture.js]
skip-if = buildapp == 'mulet' || !crashreporter
skip-if = buildapp == 'mulet' || !crashreporter || e10s # crashing the remote thumbnailer crashes the remote test tab
[browser_thumbnails_bg_crash_while_idle.js]
skip-if = buildapp == 'mulet' || !crashreporter
skip-if = buildapp == 'mulet' || !crashreporter || e10s
[browser_thumbnails_bg_basic.js]
[browser_thumbnails_bg_queueing.js]
[browser_thumbnails_bg_timeout.js]
[browser_thumbnails_bg_redirect.js]
[browser_thumbnails_bg_destroy_browser.js]
[browser_thumbnails_bg_no_cookies_sent.js]
skip-if = e10s # e10s cookie problems
[browser_thumbnails_bg_no_cookies_stored.js]
[browser_thumbnails_bg_no_auth_prompt.js]
[browser_thumbnails_bg_no_alert.js]
@ -29,12 +29,18 @@ skip-if = buildapp == 'mulet' || !crashreporter
[browser_thumbnails_bug726727.js]
skip-if = buildapp == 'mulet'
[browser_thumbnails_bug727765.js]
skip-if = e10s # tries to open crypto/local file from the child
[browser_thumbnails_bug818225.js]
skip-if = e10s # load event issues, bug 1084637.
[browser_thumbnails_capture.js]
skip-if = e10s # tries to call drawWindow with a remote browser.
[browser_thumbnails_expiration.js]
[browser_thumbnails_privacy.js]
skip-if = e10s # nsSSLStatus has null mServerCert, bug 820466
[browser_thumbnails_redirect.js]
skip-if = e10s # bug 1050869
[browser_thumbnails_storage.js]
[browser_thumbnails_storage_migrate3.js]
skip-if = buildapp == 'mulet'
[browser_thumbnails_update.js]
skip-if = e10s # tries to open crypto/local file from the child

View File

@ -137,6 +137,7 @@ function captureAndCheckColor(aRed, aGreen, aBlue, aMessage) {
/**
* For a given URL, loads the corresponding thumbnail
* to a canvas and passes its image data to the callback.
* Note, not compat with e10s!
* @param aURL The url associated with the thumbnail.
* @param aCallback The function to pass the image data to.
*/

View File

@ -11,6 +11,9 @@ Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
"resource://gre/modules/PageThumbUtils.jsm");
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
"@mozilla.org/xre/app-info;1",
@ -368,6 +371,37 @@ addMessageListener("UpdateCharacterSet", function (aMessage) {
docShell.gatherCharsetMenuTelemetry();
});
/**
* Remote thumbnail request handler for PageThumbs thumbnails.
*/
addMessageListener("Browser:Thumbnail:Request", function (aMessage) {
let thumbnail = content.document.createElementNS(PageThumbUtils.HTML_NAMESPACE,
"canvas");
thumbnail.mozOpaque = true;
thumbnail.mozImageSmoothingEnabled = true;
thumbnail.width = aMessage.data.canvasWidth;
thumbnail.height = aMessage.data.canvasHeight;
let [width, height, scale] =
PageThumbUtils.determineCropSize(content, thumbnail);
let ctx = thumbnail.getContext("2d");
ctx.save();
ctx.scale(scale, scale);
ctx.drawWindow(content, 0, 0, width, height,
aMessage.data.background,
ctx.DRAWWINDOW_DO_NOT_FLUSH);
ctx.restore();
thumbnail.toBlob(function (aBlob) {
sendAsyncMessage("Browser:Thumbnail:Response", {
thumbnail: aBlob,
id: aMessage.data.id
});
});
});
// The AddonsChild needs to be rooted so that it stays alive as long as
// the tab.
let AddonsChild;

View File

@ -51,7 +51,8 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
*/
"markers" : {
type: "markers",
markers: Arg(0, "array:json")
markers: Arg(0, "array:json"),
endTime: Arg(1, "number")
},
/**
@ -80,6 +81,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
this.tabActor = tabActor;
this._isRecording = false;
this._startTime = 0;
// Make sure to get markers from new windows as they become available
this._onWindowReady = this._onWindowReady.bind(this);
@ -142,7 +144,8 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
markers = [...markers, ...docShell.popProfileTimelineMarkers()];
}
if (markers.length > 0) {
events.emit(this, "markers", markers);
let endTime = this.docShells[0].now();
events.emit(this, "markers", markers, endTime);
}
if (this._memoryActor) {
events.emit(this, "memory", Date.now(), this._memoryActor.measure());
@ -176,6 +179,7 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
return;
}
this._isRecording = true;
this._startTime = this.docShells[0].now();
for (let docShell of this.docShells) {
docShell.recordProfileTimelineMarkers = true;
@ -191,10 +195,14 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
}
this._pullTimelineData();
return this._startTime;
}, {
request: {
withMemory: Option(0, "boolean"),
withTicks: Option(0, "boolean")
},
response: {
value: RetVal("number")
}
}),
@ -228,8 +236,6 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
*/
_onWindowReady: function({window}) {
if (this._isRecording) {
// XXX As long as bug 1070089 isn't fixed, each docShell has its own start
// recording time, so markers aren't going to be properly ordered.
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);

View File

@ -292,6 +292,36 @@ let AudioNodeActor = exports.AudioNodeActor = protocol.ActorClass({
response: { params: RetVal("json") }
}),
/**
* Connects this audionode to an AudioParam via `node.connect(param)`.
*/
connectParam: method(function (destActor, paramName, output) {
let srcNode = this.node.get();
let destNode = destActor.node.get();
if (srcNode === null || destNode === null) {
return CollectedAudioNodeError();
}
try {
// Connect via the unwrapped node, so we can call the
// patched method that fires the webaudio actor's `connect-param` event.
// Connect directly to the wrapped `destNode`, otherwise
// the patched method thinks this is a new node and won't be
// able to find it in `_nativeToActorID`.
XPCNativeWrapper.unwrap(srcNode).connect(destNode[paramName], output);
} catch (e) {
return constructError(e);
}
}, {
request: {
destActor: Arg(0, "audionode"),
paramName: Arg(1, "string"),
output: Arg(2, "nullable:number")
},
response: { error: RetVal("nullable:json") }
}),
/**
* Connects this audionode to another via `node.connect(dest)`.
*/

View File

@ -135,7 +135,9 @@ var SimpleServiceDiscovery = {
_usingLAN: function() {
let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
return (network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI || network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET);
return (network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI ||
network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET ||
network.linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN);
},
_search: function _search() {

View File

@ -34,6 +34,7 @@ menubar, toolbar[type="menubar"] {
menubar:-moz-lwtheme,
toolbar:-moz-lwtheme {
-moz-appearance: none;
color: inherit;
}
/* in browser.xul, the menubar is inside a toolbar... */