--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -235,16 +235,21 @@
<menuitem id="context-video-saveimage"
accesskey="&videoSaveImage.accesskey;"
label="&videoSaveImage.label;"
oncommand="gContextMenu.saveVideoFrameAsImage();"/>
<menuitem id="context-sendvideo"
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;"
oncommand="gContextMenu.sendMedia();"/>
<menuitem id="context-ctp-play"
label="&playPluginCmd.label;"
accesskey="&playPluginCmd.accesskey;"
oncommand="gContextMenu.playPlugin();"/>
--- a/browser/base/content/browser-tabPreviews.js
+++ b/browser/base/content/browser-tabPreviews.js
@@ -42,20 +42,16 @@ var tabPreviews = {
img.src = PageThumbs.getThumbnailURL(uri);
return img;
}
return this.capture(aTab, !aTab.hasAttribute("busy"));
},
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;
// FIXME: The gBrowserThumbnails._shouldCapture determines whether
// thumbnails should be written to disk. This should somehow be part
// of the PageThumbs API. (bug 1062414)
if (aShouldCache &&
gBrowserThumbnails._shouldCapture(browser)) {
@@ -72,17 +68,17 @@ var tabPreviews = {
let canvas = PageThumbs.createCanvas(window);
if (aShouldCache) {
aTab.__thumbnail = canvas;
aTab.__thumbnail_lastURI = uri;
}
- PageThumbs.captureToCanvas(aTab.linkedBrowser.contentWindow, canvas);
+ PageThumbs.captureToCanvas(browser, canvas);
return canvas;
},
handleEvent: function tabPreviews_handleEvent(event) {
switch (event.type) {
case "TabSelect":
if (this._selectedTab &&
this._selectedTab.parentNode &&
--- a/browser/base/content/browser-thumbnails.js
+++ b/browser/base/content/browser-thumbnails.js
@@ -28,39 +28,31 @@ let gBrowserThumbnails = {
_timeouts: null,
/**
* List of tab events we want to listen for.
*/
_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);
this._sslDiskCacheEnabled =
Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
this._tabEvents.forEach(function (aEvent) {
gBrowser.tabContainer.addEventListener(aEvent, this, false);
}, this);
this._timeouts = new WeakMap();
},
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);
this._tabEvents.forEach(function (aEvent) {
gBrowser.tabContainer.removeEventListener(aEvent, this, false);
}, this);
},
@@ -120,43 +112,43 @@ let gBrowserThumbnails = {
this._capture(aBrowser);
}.bind(this), this._captureDelayMS);
this._timeouts.set(aBrowser, timeout);
},
// 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;
// Don't capture in per-window private browsing mode.
if (PrivateBrowsingUtils.isWindowPrivate(window))
return false;
let doc = aBrowser.contentDocument;
// FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
// that currently regresses Talos SVG tests.
if (doc instanceof SVGDocument || doc instanceof XMLDocument)
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;
- // Don't take screenshots of about: pages.
- if (aBrowser.currentURI.schemeIs("about"))
- return false;
-
let channel = aBrowser.docShell.currentDocumentChannel;
// No valid document channel. We shouldn't take a screenshot.
if (!channel)
return false;
// Don't take screenshots of internally redirecting about: pages.
// This includes error pages.
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -183,16 +183,22 @@ XPCOMUtils.defineLazyModuleGetter(this,
#endif
XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
"resource:///modules/FormValidationHandler.jsm");
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",
"about:home",
"about:privatebrowsing",
"about:welcomeback",
"about:sessionrestore"
];
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -203,19 +203,29 @@ nsContextMenu.prototype = {
this.showItem("context-savevideo", this.onVideo);
this.showItem("context-saveaudio", this.onAudio);
this.showItem("context-video-saveimage", this.onVideo);
this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
// 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() {
// View source is always OK, unless in directory listing.
this.showItem("context-viewpartialsource-selection",
this.isContentSelected);
this.showItem("context-viewpartialsource-mathml",
this.onMathML && !this.isContentSelected);
@@ -1311,16 +1321,35 @@ nsContextMenu.prototype = {
if (this.onCanvas || this.onImage)
this.sendMedia();
},
sendMedia: function() {
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");
},
hidePlugin: function() {
gPluginHandler.contextMenuCommand(this.browser, this.target, "hide");
},
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -702,17 +702,17 @@
// canvas size (in CSS pixels) to the window's backing resolution in order
// to get a full-resolution drag image for use on HiDPI displays.
let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
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();
]]></handler>
<handler event="dragend"><![CDATA[
let dt = event.dataTransfer;
let draggedChat = dt.mozGetDataAt("application/x-moz-chatbox", 0);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -4525,17 +4525,17 @@
let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.mozOpaque = true;
canvas.width = 160 * scale;
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);
// _dragData.offsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
// dragend such that the mouse appears to have the same position
// relative to the corner of the dragged tab.
function clientX(ele) ele.getBoundingClientRect().left;
--- a/browser/base/content/test/general/test_contextmenu.html
+++ b/browser/base/content/test/general/test_contextmenu.html
@@ -169,17 +169,19 @@ function runTest(testNum) {
"context-video-showstats", true,
"context-video-fullscreen", true,
"---", null,
"context-viewvideo", true,
"context-copyvideourl", true,
"---", 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.
},
function () {
// Context menu for a video (with an audio-only file)
checkContextMenu(["context-media-play", true,
@@ -213,17 +215,19 @@ function runTest(testNum) {
"context-video-showstats", false,
"context-video-fullscreen", false,
"---", null,
"context-viewvideo", true,
"context-copyvideourl", true,
"---", 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.
},
function () {
// Context menu for a video (with an INVALID media source)
checkContextMenu(["context-media-play", false,
@@ -237,17 +241,19 @@ function runTest(testNum) {
"context-video-showstats", false,
"context-video-fullscreen", false,
"---", null,
"context-viewvideo", false,
"context-copyvideourl", false,
"---", 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.
},
function () {
// Context menu for an iframe
checkContextMenu(["context-navigation", null,
@@ -296,16 +302,18 @@ function runTest(testNum) {
"context-video-fullscreen", true,
"---", null,
"context-viewvideo", true,
"context-copyvideourl", true,
"---", null,
"context-savevideo", true,
"context-video-saveimage", true,
"context-sendvideo", true,
+ "context-castvideo", null,
+ [], null,
"frame", null,
["context-showonlythisframe", true,
"context-openframeintab", true,
"context-openframe", true,
"---", null,
"context-reloadframe", true,
"---", null,
"context-bookmarkframe", true,
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -919,17 +919,17 @@ const CustomizableWidgets = [
onCommand: function(aEvent) {
let win = aEvent.view;
win.MailIntegration.sendLinkForWindow(win.content);
}
}, {
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) {
let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
node.setAttribute("id", this.id);
node.classList.add("toolbarbutton-1");
node.classList.add("chromeclass-toolbar-additional");
node.classList.add("badged-button");
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -94,16 +94,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource:///modules/BrowserUITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
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");
#endif
XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
"resource:///modules/ContentSearch.jsm");
@@ -742,18 +745,40 @@ BrowserGlue.prototype = {
if (Services.prefs.getBoolPref("dom.identity.enabled")) {
SignInToWebsiteUX.uninit();
}
#endif
webrtcUI.uninit();
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();
// Load the "more info" page for a locked places.sqlite
// This property is set earlier by places-database-locked topic.
if (this._isPlacesDatabaseLocked) {
this._showPlacesLockedNotificationBox();
@@ -1339,17 +1364,17 @@ BrowserGlue.prototype = {
var notifyBox = win.gBrowser.getNotificationBox();
var notification = notifyBox.appendNotification(text, title, null,
notifyBox.PRIORITY_CRITICAL_MEDIUM,
buttons);
notification.persistence = -1; // Until user closes it
},
_migrateUI: function BG__migrateUI() {
- const UI_VERSION = 23;
+ const UI_VERSION = 24;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0;
try {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
} catch(ex) {}
if (currentUIVersion >= UI_VERSION)
return;
@@ -1417,25 +1442,16 @@ BrowserGlue.prototype = {
};
if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
}
}
}
- 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");
// Since the Downloads button is located in the navigation bar by default,
// migration needs to happen only if the toolbar was customized using a
// previous UI version, and the button was not already placed on the
// toolbar manually.
@@ -1490,20 +1506,16 @@ BrowserGlue.prototype = {
if (currentset.contains("bookmarks-menu-button-container")) {
currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
"$1bookmarks-menu-button$2");
xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
}
}
}
- 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,
"chromeappsstore.sqlite");
OS.File.remove(path);
}
if (currentUIVersion < 16) {
@@ -1588,16 +1600,28 @@ BrowserGlue.prototype = {
try {
let name = Services.prefs.getComplexValue(kSelectedEnginePref,
Ci.nsIPrefLocalizedString).data;
Services.search.currentEngine = Services.search.getEngineByName(name);
} catch (ex) {}
}
}
+ 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);
},
// ------------------------------
// public nsIBrowserGlue members
// ------------------------------
--- a/browser/components/shell/nsGNOMEShellService.cpp
+++ b/browser/components/shell/nsGNOMEShellService.cpp
@@ -308,52 +308,53 @@ nsGNOMEShellService::SetDefaultBrowser(b
// Add mime types for html, xhtml extension and set app to just created appinfo.
for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) {
appInfo->SetAsDefaultForMimeType(nsDependentCString(appTypes[i].mimeType));
appInfo->SetAsDefaultForFileExtensions(nsDependentCString(appTypes[i].extensions));
}
}
}
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ }
+
return NS_OK;
}
NS_IMETHODIMP
nsGNOMEShellService::GetShouldCheckDefaultBrowser(bool* aResult)
{
// If we've already checked, the browser has been started and this is a
// new window open, and we don't want to check again.
if (mCheckedThisSession) {
*aResult = false;
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
nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult)
{
// setting desktop background is currently only supported
// for Gnome or desktops using the same GSettings and GConf keys
const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID");
--- a/browser/components/shell/nsMacShellService.cpp
+++ b/browser/components/shell/nsMacShellService.cpp
@@ -85,51 +85,54 @@ nsMacShellService::SetDefaultBrowser(boo
if (aClaimAllTypes) {
if (::LSSetDefaultHandlerForURLScheme(CFSTR("ftp"), firefoxID) != noErr) {
return NS_ERROR_FAILURE;
}
if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, firefoxID) != noErr) {
return NS_ERROR_FAILURE;
}
}
-
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ }
+
return NS_OK;
}
NS_IMETHODIMP
nsMacShellService::GetShouldCheckDefaultBrowser(bool* aResult)
{
// If we've already checked, the browser has been started and this is a
// new window open, and we don't want to check again.
if (mCheckedThisSession) {
*aResult = false;
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
nsMacShellService::GetCanSetDesktopBackground(bool* aResult)
{
*aResult = true;
return NS_OK;
}
--- a/browser/components/shell/nsWindowsShellService.cpp
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -270,48 +270,43 @@ nsWindowsShellService::ShortcutMaintenan
if (!isSupported)
return NS_OK;
nsAutoString appId;
if (NS_FAILED(taskbarInfo->GetDefaultGroupId(appId)))
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);
if (!version.IsEmpty() && version.Equals(appId)) {
// We're all good, get out of here.
return NS_OK;
}
}
// Update the version in prefs
prefString =
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
if (NS_FAILED(rv))
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;
}
nsAutoString appHelperPath;
if (NS_FAILED(GetHelperPath(appHelperPath)))
return NS_ERROR_UNEXPECTED;
@@ -702,53 +697,53 @@ nsWindowsShellService::SetDefaultBrowser
// The above calls hould never really fail, but just in case
// fallb ack to showing control panel for all defaults
if (NS_FAILED(rv)) {
rv = LaunchControlPanelDefaultPrograms();
}
}
}
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ }
+
return rv;
}
NS_IMETHODIMP
nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
// If we've already checked, the browser has been started and this is a
// new window open, and we don't want to check again.
if (mCheckedThisSession) {
*aResult = false;
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);
}
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);
}
static nsresult
WriteBitmap(nsIFile* aFile, imgIContainer* aImage)
{
nsresult rv;
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -1378,25 +1378,19 @@ TabCanvas.prototype = Utils.extend(new S
// ----------
// Function: paint
paint: function TabCanvas_paint(evt) {
var w = this.canvas.width;
var h = this.canvas.height;
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");
+ });
},
// ----------
// Function: toImageData
toImageData: function TabCanvas_toImageData() {
return this.canvas.toDataURL("image/png");
}
});
--- a/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-01.js
+++ b/browser/devtools/timeline/test/browser_timeline_overview-initial-selection-01.js
@@ -24,17 +24,17 @@ let test = Task.async(function*() {
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
let markers = TimelineController.getMarkers();
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,
(selectionRatio * TimelineView.overview.width) | 0,
"The initial selection end is correct.");
yield teardown(panel);
finish();
--- a/browser/devtools/timeline/timeline.js
+++ b/browser/devtools/timeline/timeline.js
@@ -102,23 +102,29 @@ let TimelineController = {
yield this._stopRecording();
}
}),
/**
* Starts the recording, updating the UI as needed.
*/
_startRecording: function*() {
+ TimelineView.handleRecordingStarted();
+ 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 = performance.now();
- this._markers.endTime = performance.now();
+ this._markers.startTime = startTime;
+ this._markers.endTime = startTime;
this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
-
- TimelineView.handleRecordingStarted();
- yield gFront.start();
},
/**
* Stops the recording, updating the UI as needed.
*/
_stopRecording: function*() {
clearInterval(this._updateId);
@@ -137,27 +143,36 @@ let TimelineController = {
},
/**
* Callback handling the "markers" event on the timeline front.
*
* @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;
},
/**
* Callback invoked at a fixed interval while recording.
* 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);
}
};
/**
* Functions handling the timeline frontend view.
*/
let TimelineView = {
@@ -209,22 +224,22 @@ let TimelineView = {
handleRecordingEnded: function() {
$("#record-button").removeAttribute("checked");
$("#timeline-pane").selectedPanel = $("#timeline-waterfall");
this.overview.selectionEnabled = true;
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);
},
/**
* Signals that a new set of markers was made available by the controller,
* or that the overview graph needs to be updated.
@@ -246,18 +261,18 @@ let TimelineView = {
this.waterfall.clearView();
return;
}
let selection = this.overview.getSelection();
let start = selection.start / this.overview.dataScaleX;
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);
},
/**
* Callback handling the "refresh" event on the timeline overview.
*/
_onRefresh: function() {
this.waterfall.recalculateBounds();
--- a/browser/devtools/timeline/widgets/overview.js
+++ b/browser/devtools/timeline/widgets/overview.js
@@ -165,16 +165,19 @@ Overview.prototype = Heritage.extend(Abs
gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[0], style.stroke);
gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[1], style.fill);
gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[2], style.fill);
gradient.addColorStop(OVERVIEW_MARKERS_COLOR_STOPS[3], style.stroke);
ctx.fillStyle = gradient;
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);
ctx.rect(left, top, width, height);
}
ctx.fill();
--- a/browser/devtools/timeline/widgets/waterfall.js
+++ b/browser/devtools/timeline/widgets/waterfall.js
@@ -71,26 +71,27 @@ function Waterfall(parent) {
Waterfall.prototype = {
/**
* Populates this view with the provided data source.
*
* @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);
},
/**
* Depopulates this view.
*/
clearView: function() {
while (this._headerContents.hasChildNodes()) {
--- a/browser/devtools/webaudioeditor/test/browser.ini
+++ b/browser/devtools/webaudioeditor/test/browser.ini
@@ -18,16 +18,17 @@ support-files =
[browser_audionode-actor-get-param-flags.js]
[browser_audionode-actor-get-params-01.js]
[browser_audionode-actor-get-params-02.js]
[browser_audionode-actor-get-set-param.js]
[browser_audionode-actor-get-type.js]
[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]
[browser_wa_destroy-node-01.js]
[browser_wa_first-run.js]
[browser_wa_reset-01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_audionode-actor-connectparam.js
@@ -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();
+}
+
--- a/browser/devtools/webide/content/addons.js
+++ b/browser/devtools/webide/content/addons.js
@@ -25,23 +25,24 @@ window.addEventListener("unload", functi
ForgetAddonsList();
}, true);
function CloseUI() {
window.parent.UI.openProject();
}
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) {
case "update":
progress.removeAttribute("value");
li.setAttribute("status", addon.status);
status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
break;
@@ -68,30 +69,39 @@ function BuildItem(addon, isADB) {
for (let e of events) {
addon.off(e, onAddonUpdate);
}
});
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);
let status = document.createElement("span");
status.className = "status";
status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
li.appendChild(status);
@@ -106,17 +116,17 @@ function BuildItem(addon, isADB) {
uninstallButton.className = "uninstall-button";
uninstallButton.onclick = () => addon.uninstall();
uninstallButton.textContent = Strings.GetStringFromName("addons_uninstall_button");
li.appendChild(uninstallButton);
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";
li.appendChild(warning);
}
document.querySelector("ul").appendChild(li);
}
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -64,25 +64,32 @@ let UI = {
this.onfocus = this.onfocus.bind(this);
window.addEventListener("focus", this.onfocus, true);
AppProjects.load().then(() => {
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");
this.setupDeck();
},
uninit: function() {
window.removeEventListener("focus", this.onfocus, true);
--- a/browser/devtools/webide/modules/addons.js
+++ b/browser/devtools/webide/modules/addons.js
@@ -8,59 +8,49 @@ const {AddonManager} = Cu.import("resour
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
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 = "";
if (platform.indexOf("Win") != -1) {
OS = "win32";
} else if (platform.indexOf("Mac") != -1) {
OS = "mac64";
} else if (platform.indexOf("Linux") != -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() {
if (!GetAvailableAddons_promise) {
let deferred = promise.defer();
GetAvailableAddons_promise = deferred.promise;
let addons = {
simulators: [],
@@ -68,16 +58,17 @@ let GetAvailableAddons = exports.GetAvai
}
GetAddonsJSON(true).then(json => {
for (let stability in json) {
for (let version of json[stability]) {
addons.simulators.push(new SimulatorAddon(stability, version));
}
}
addons.adb = new ADBAddon();
+ addons.adapters = new AdaptersAddon();
deferred.resolve(addons);
}, e => {
GetAvailableAddons_promise = null;
deferred.reject(e);
});
}
return GetAvailableAddons_promise;
}
@@ -94,33 +85,42 @@ Addon.prototype = {
this._status = value;
this.emit("update");
}
},
get status() {
return this._status;
},
+ updateInstallStatus: function() {
+ AddonManager.getAddonByID(this.addonID, (addon) => {
+ if (addon && !addon.userDisabled) {
+ this.status = "installed";
+ } else {
+ this.status = "uninstalled";
+ }
+ });
+ },
+
install: function() {
- if (this.status != "uninstalled") {
- throw new Error("Not uninstalled");
- }
- this.status = "preparing";
-
AddonManager.getAddonByID(this.addonID, (addon) => {
+ if (addon && !addon.userDisabled) {
+ this.status = "installed";
+ return;
+ }
+ this.status = "preparing";
if (addon && addon.userDisabled) {
addon.userDisabled = false;
} else {
AddonManager.getInstallForURL(this.xpiLink, (install) => {
install.addListener(this);
install.install();
}, "application/x-xpinstall");
}
});
-
},
uninstall: function() {
AddonManager.getAddonByID(this.addonID, (addon) => {
addon.uninstall();
});
},
@@ -162,49 +162,35 @@ Addon.prototype = {
this.installFailureHandler(install, "Install failed");
},
}
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);
new file mode 100644
index 0000000000000000000000000000000000000000..5a512ae3d199746350c650e527b0178411b0440e
GIT binary patch
literal 1156
zc$^FHW@Zs#U|`^2SQYE%`bJKz+?|nuA%=;8frmkcAt^t<q`0Igu|O}YI5dQlfqBCR
zn~+UFTw1}+z{v6qs1&TbcY?1sv!OuS``_AZ{<)gcFE?JAti-{^y0_v?TKe6IIcX<-
zqPG6HKetY!Dqv5_r;mHyTML^v^YVH-vQE9ql(IXXFS<suHtVXnLGjet2c7+yzQ+_f
z?06#faMovrGWDgE#}e3hZYgU#b8Ogs_+j6D4LAPRmt43+&pepMq;}9UCoAI0bJIH|
z4-`NA__Z|h{-JB!Dq$I(Cc(iX(KjZ2W1H|M*Q)E;#}lkg{s(%iHcb-P<FwvNZ?1IT
zP1EP<>GRHAirFi_jBAd|!$}FLlX(`OoaE(uGU|Pe!_FTnr~DUbxK^)ORD1m$m$_Nj
zp7#@jCObx|r%OKl;5zO9l#5jv-Y$i@c~dOAtXIBDmpr#j+<kfJvqqNbk2n51|53$C
z;%wsgytL5ppnyj#DR+NMsILvTZ|{k-@a6s0CNW`aPrXv@#cA7L>MvUozuAr<03I3B
zfsygsZq}Tiz!1L9%)r16jEu~@;*!Li9KE8HG;mz>0^^|<GcHntV*6(s2-G|e&)Ioh
zilb9c-fKsguX5<F$$q(^EBm>1I-@sC+}jef`rr3o3J;q;wb)v3-fUg*w_uf*#iWfv
zt9$J^mE=8At)^$~|0u0@<WRlGp0C!54}LpF{a7YFS+M4k<w3vMTrz31zlf*utZC$D
z@maQMLHD~)Oa7nO<WiA$DJV^jfumxIq^9@#hY}7lE{rvr42qY(o?cK@*CM8?adv|D
zX}4v(!F+3<G%I`DHrGmdx$Dmr-jo#%%X==>iZB|<%y=Ct5w^E}!CAYKSm)>BYzi)q
zDqpX^ExY6E)Vn{@%WiLTw0?VH^3x(ow?c{Ihn}=<(C_-FGG*`Q>a$&|Cce3)(YGkl
z<Lc@U=lt)id98c%%!ygCry5q~-Du6e@=7PlGgb0*P{@iD$7$9RCkJkwR2ej<$ZFHI
zrJ^&E-+tOEzwXVoM1N_O=l5rxd9<!%CtqpZO3jzqcMCO^L~Z7le%<2hEc=jGDx!Cl
z;qSR35x3?Qv+bGp@mEOMrGSP0vFDn#XW4!{zN_p#L#0H9%sE!+vj5&Um1emejY%nN
zNh-6~K4R4C;j_%*sH*Ps%SYeb*J$eAHR;vy%CA4{ItngrTzA1x+Ml^v&c*gS@9r~F
z_jqnUuh@MjRLV`e?YvgIdeR-G#~Ko?qN>Z+`IgIY{hS&afByKhduJV9U$A|3<TrnS
zHzSiAGp@`m!2kk4F2j;W5DPVHvqG{qT2ewb5i><1o7fAaP|_zWBz<BS$jS!N!~}$k
Kf%Iu+5Dx$(df(Lm
new file mode 100644
index 0000000000000000000000000000000000000000..5a512ae3d199746350c650e527b0178411b0440e
GIT binary patch
literal 1156
zc$^FHW@Zs#U|`^2SQYE%`bJKz+?|nuA%=;8frmkcAt^t<q`0Igu|O}YI5dQlfqBCR
zn~+UFTw1}+z{v6qs1&TbcY?1sv!OuS``_AZ{<)gcFE?JAti-{^y0_v?TKe6IIcX<-
zqPG6HKetY!Dqv5_r;mHyTML^v^YVH-vQE9ql(IXXFS<suHtVXnLGjet2c7+yzQ+_f
z?06#faMovrGWDgE#}e3hZYgU#b8Ogs_+j6D4LAPRmt43+&pepMq;}9UCoAI0bJIH|
z4-`NA__Z|h{-JB!Dq$I(Cc(iX(KjZ2W1H|M*Q)E;#}lkg{s(%iHcb-P<FwvNZ?1IT
zP1EP<>GRHAirFi_jBAd|!$}FLlX(`OoaE(uGU|Pe!_FTnr~DUbxK^)ORD1m$m$_Nj
zp7#@jCObx|r%OKl;5zO9l#5jv-Y$i@c~dOAtXIBDmpr#j+<kfJvqqNbk2n51|53$C
z;%wsgytL5ppnyj#DR+NMsILvTZ|{k-@a6s0CNW`aPrXv@#cA7L>MvUozuAr<03I3B
zfsygsZq}Tiz!1L9%)r16jEu~@;*!Li9KE8HG;mz>0^^|<GcHntV*6(s2-G|e&)Ioh
zilb9c-fKsguX5<F$$q(^EBm>1I-@sC+}jef`rr3o3J;q;wb)v3-fUg*w_uf*#iWfv
zt9$J^mE=8At)^$~|0u0@<WRlGp0C!54}LpF{a7YFS+M4k<w3vMTrz31zlf*utZC$D
z@maQMLHD~)Oa7nO<WiA$DJV^jfumxIq^9@#hY}7lE{rvr42qY(o?cK@*CM8?adv|D
zX}4v(!F+3<G%I`DHrGmdx$Dmr-jo#%%X==>iZB|<%y=Ct5w^E}!CAYKSm)>BYzi)q
zDqpX^ExY6E)Vn{@%WiLTw0?VH^3x(ow?c{Ihn}=<(C_-FGG*`Q>a$&|Cce3)(YGkl
z<Lc@U=lt)id98c%%!ygCry5q~-Du6e@=7PlGgb0*P{@iD$7$9RCkJkwR2ej<$ZFHI
zrJ^&E-+tOEzwXVoM1N_O=l5rxd9<!%CtqpZO3jzqcMCO^L~Z7le%<2hEc=jGDx!Cl
z;qSR35x3?Qv+bGp@mEOMrGSP0vFDn#XW4!{zN_p#L#0H9%sE!+vj5&Um1emejY%nN
zNh-6~K4R4C;j_%*sH*Ps%SYeb*J$eAHR;vy%CA4{ItngrTzA1x+Ml^v&c*gS@9r~F
z_jqnUuh@MjRLV`e?YvgIdeR-G#~Ko?qN>Z+`IgIY{hS&afByKhduJV9U$A|3<TrnS
zHzSiAGp@`m!2kk4F2j;W5DPVHvqG{qT2ewb5i><1o7fAaP|_zWBz<BS$jS!N!~}$k
Kf%Iu+5Dx$(df(Lm
new file mode 100644
index 0000000000000000000000000000000000000000..5a512ae3d199746350c650e527b0178411b0440e
GIT binary patch
literal 1156
zc$^FHW@Zs#U|`^2SQYE%`bJKz+?|nuA%=;8frmkcAt^t<q`0Igu|O}YI5dQlfqBCR
zn~+UFTw1}+z{v6qs1&TbcY?1sv!OuS``_AZ{<)gcFE?JAti-{^y0_v?TKe6IIcX<-
zqPG6HKetY!Dqv5_r;mHyTML^v^YVH-vQE9ql(IXXFS<suHtVXnLGjet2c7+yzQ+_f
z?06#faMovrGWDgE#}e3hZYgU#b8Ogs_+j6D4LAPRmt43+&pepMq;}9UCoAI0bJIH|
z4-`NA__Z|h{-JB!Dq$I(Cc(iX(KjZ2W1H|M*Q)E;#}lkg{s(%iHcb-P<FwvNZ?1IT
zP1EP<>GRHAirFi_jBAd|!$}FLlX(`OoaE(uGU|Pe!_FTnr~DUbxK^)ORD1m$m$_Nj
zp7#@jCObx|r%OKl;5zO9l#5jv-Y$i@c~dOAtXIBDmpr#j+<kfJvqqNbk2n51|53$C
z;%wsgytL5ppnyj#DR+NMsILvTZ|{k-@a6s0CNW`aPrXv@#cA7L>MvUozuAr<03I3B
zfsygsZq}Tiz!1L9%)r16jEu~@;*!Li9KE8HG;mz>0^^|<GcHntV*6(s2-G|e&)Ioh
zilb9c-fKsguX5<F$$q(^EBm>1I-@sC+}jef`rr3o3J;q;wb)v3-fUg*w_uf*#iWfv
zt9$J^mE=8At)^$~|0u0@<WRlGp0C!54}LpF{a7YFS+M4k<w3vMTrz31zlf*utZC$D
z@maQMLHD~)Oa7nO<WiA$DJV^jfumxIq^9@#hY}7lE{rvr42qY(o?cK@*CM8?adv|D
zX}4v(!F+3<G%I`DHrGmdx$Dmr-jo#%%X==>iZB|<%y=Ct5w^E}!CAYKSm)>BYzi)q
zDqpX^ExY6E)Vn{@%WiLTw0?VH^3x(ow?c{Ihn}=<(C_-FGG*`Q>a$&|Cce3)(YGkl
z<Lc@U=lt)id98c%%!ygCry5q~-Du6e@=7PlGgb0*P{@iD$7$9RCkJkwR2ej<$ZFHI
zrJ^&E-+tOEzwXVoM1N_O=l5rxd9<!%CtqpZO3jzqcMCO^L~Z7le%<2hEc=jGDx!Cl
z;qSR35x3?Qv+bGp@mEOMrGSP0vFDn#XW4!{zN_p#L#0H9%sE!+vj5&Um1emejY%nN
zNh-6~K4R4C;j_%*sH*Ps%SYeb*J$eAHR;vy%CA4{ItngrTzA1x+Ml^v&c*gS@9r~F
z_jqnUuh@MjRLV`e?YvgIdeR-G#~Ko?qN>Z+`IgIY{hS&afByKhduJV9U$A|3<TrnS
zHzSiAGp@`m!2kk4F2j;W5DPVHvqG{qT2ewb5i><1o7fAaP|_zWBz<BS$jS!N!~}$k
Kf%Iu+5Dx$(df(Lm
new file mode 100644
index 0000000000000000000000000000000000000000..5a512ae3d199746350c650e527b0178411b0440e
GIT binary patch
literal 1156
zc$^FHW@Zs#U|`^2SQYE%`bJKz+?|nuA%=;8frmkcAt^t<q`0Igu|O}YI5dQlfqBCR
zn~+UFTw1}+z{v6qs1&TbcY?1sv!OuS``_AZ{<)gcFE?JAti-{^y0_v?TKe6IIcX<-
zqPG6HKetY!Dqv5_r;mHyTML^v^YVH-vQE9ql(IXXFS<suHtVXnLGjet2c7+yzQ+_f
z?06#faMovrGWDgE#}e3hZYgU#b8Ogs_+j6D4LAPRmt43+&pepMq;}9UCoAI0bJIH|
z4-`NA__Z|h{-JB!Dq$I(Cc(iX(KjZ2W1H|M*Q)E;#}lkg{s(%iHcb-P<FwvNZ?1IT
zP1EP<>GRHAirFi_jBAd|!$}FLlX(`OoaE(uGU|Pe!_FTnr~DUbxK^)ORD1m$m$_Nj
zp7#@jCObx|r%OKl;5zO9l#5jv-Y$i@c~dOAtXIBDmpr#j+<kfJvqqNbk2n51|53$C
z;%wsgytL5ppnyj#DR+NMsILvTZ|{k-@a6s0CNW`aPrXv@#cA7L>MvUozuAr<03I3B
zfsygsZq}Tiz!1L9%)r16jEu~@;*!Li9KE8HG;mz>0^^|<GcHntV*6(s2-G|e&)Ioh
zilb9c-fKsguX5<F$$q(^EBm>1I-@sC+}jef`rr3o3J;q;wb)v3-fUg*w_uf*#iWfv
zt9$J^mE=8At)^$~|0u0@<WRlGp0C!54}LpF{a7YFS+M4k<w3vMTrz31zlf*utZC$D
z@maQMLHD~)Oa7nO<WiA$DJV^jfumxIq^9@#hY}7lE{rvr42qY(o?cK@*CM8?adv|D
zX}4v(!F+3<G%I`DHrGmdx$Dmr-jo#%%X==>iZB|<%y=Ct5w^E}!CAYKSm)>BYzi)q
zDqpX^ExY6E)Vn{@%WiLTw0?VH^3x(ow?c{Ihn}=<(C_-FGG*`Q>a$&|Cce3)(YGkl
z<Lc@U=lt)id98c%%!ygCry5q~-Du6e@=7PlGgb0*P{@iD$7$9RCkJkwR2ej<$ZFHI
zrJ^&E-+tOEzwXVoM1N_O=l5rxd9<!%CtqpZO3jzqcMCO^L~Z7le%<2hEc=jGDx!Cl
z;qSR35x3?Qv+bGp@mEOMrGSP0vFDn#XW4!{zN_p#L#0H9%sE!+vj5&Um1emejY%nN
zNh-6~K4R4C;j_%*sH*Ps%SYeb*J$eAHR;vy%CA4{ItngrTzA1x+Ml^v&c*gS@9r~F
z_jqnUuh@MjRLV`e?YvgIdeR-G#~Ko?qN>Z+`IgIY{hS&afByKhduJV9U$A|3<TrnS
zHzSiAGp@`m!2kk4F2j;W5DPVHvqG{qT2ewb5i><1o7fAaP|_zWBz<BS$jS!N!~}$k
Kf%Iu+5Dx$(df(Lm
--- a/browser/devtools/webide/test/chrome.ini
+++ b/browser/devtools/webide/test/chrome.ini
@@ -15,16 +15,20 @@ support-files =
addons/fxos_3_0_simulator-linux.xpi
addons/fxos_3_0_simulator-linux64.xpi
addons/fxos_3_0_simulator-win32.xpi
addons/fxos_3_0_simulator-mac64.xpi
addons/adbhelper-linux.xpi
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
[test_basic.html]
[test_newapp.html]
[test_import.html]
[test_duplicate_import.html]
--- a/browser/devtools/webide/test/head.js
+++ b/browser/devtools/webide/test/head.js
@@ -22,35 +22,32 @@ if (window.location === "chrome://browse
}
Services.prefs.setBoolPref("devtools.webide.enabled", true);
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();
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
let win = ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad);
--- a/browser/devtools/webide/test/test_addons.html
+++ b/browser/devtools/webide/test/test_addons.html
@@ -104,20 +104,20 @@
win.Cmds.showAddons();
let frame = win.document.querySelector("#deck-panel-addons");
let addonDoc = frame.contentWindow.document;
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");
info("Uninstalling Simulator 2.0");
yield installSimulatorFromUI(addonDoc, "2.0");
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -1,18 +1,21 @@
# -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
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");
pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
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", "");
--- a/browser/fuel/test/browser_ApplicationPrefs.js
+++ b/browser/fuel/test/browser_ApplicationPrefs.js
@@ -122,17 +122,17 @@ function test() {
var allPrefs = Application.prefs.all;
ok(allPrefs.length >= 800, "Check 'Application.prefs.all' for the right number of preferences");
ok(allPrefs[0].name.length > 0, "Check 'Application.prefs.all' for a valid name in the starting preference");
// test the value of the preference root
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
var pref = Application.prefs.get(testdata.string);
ok(!pref.locked, "A single preference should not be locked.");
pref.locked = true;
ok(pref.locked, "A single preference should be locked.");
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -479,16 +479,18 @@ These should match what Safari and other
<!ENTITY saveVideoCmd.label "Save Video As…">
<!ENTITY saveVideoCmd.accesskey "v">
<!ENTITY saveAudioCmd.label "Save Audio As…">
<!ENTITY saveAudioCmd.accesskey "v">
<!ENTITY emailImageCmd.label "Email Image…">
<!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">
<!ENTITY playPluginCmd.accesskey "c">
<!ENTITY hidePluginCmd.label "Hide this plugin">
<!ENTITY hidePluginCmd.accesskey "H">
<!ENTITY copyLinkCmd.label "Copy Link Location">
<!ENTITY copyLinkCmd.accesskey "a">
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -95,17 +95,17 @@ email-link-button.tooltiptext3 = Email a
quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
# LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
# %2$S is the keyboard shortcut
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
panic-button.label = Forget
panic-button.tooltiptext = Forget about some browsing history
web-apps-button.label = Apps
--- a/browser/locales/en-US/chrome/browser/devtools/webide.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webide.properties
@@ -47,16 +47,17 @@ addons_unstable=unstable
# LOCALIZATION NOTE (addons_simulator_label): This label is shown as the name of
# a given simulator version in the "Manage Simulators" pane. %1$S: Firefox OS
# version in the simulator, ex. 1.3. %2$S: Simulator stability label, ex.
# "stable" or "unstable".
addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
addons_install_button=install
addons_uninstall_button=uninstall
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
addons_status_uninstalled=Not Installed
addons_status_preparing=preparing
addons_status_downloading=downloading
addons_status_installing=installing
new file mode 100644
--- /dev/null
+++ b/browser/modules/CastingApps.jsm
@@ -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;
+ }
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -9,16 +9,17 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
XPCSHELL_TESTS_MANIFESTS += [
'test/unit/social/xpcshell.ini',
'test/xpcshell/xpcshell.ini',
]
EXTRA_JS_MODULES += [
'BrowserNewTabPreloader.jsm',
'BrowserUITelemetry.jsm',
+ 'CastingApps.jsm',
'Chat.jsm',
'ContentClick.jsm',
'ContentLinkHandler.jsm',
'ContentSearch.jsm',
'ContentWebRTC.jsm',
'CustomizationTabPreloader.jsm',
'DirectoryLinksProvider.jsm',
'E10SUtils.jsm',
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -160,19 +160,21 @@
color: white;
}
#toolbar-menubar:not(:-moz-lwtheme) {
text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4);
}
/* 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;
}
#main-window[sizemode=normal] #browser-border-start,
#main-window[sizemode=normal] #browser-border-end {
display: -moz-box;
background-color: @toolbarShadowColor@;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2829,34 +2829,34 @@ NS_IMETHODIMP
nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
{
#ifdef MOZ_ENABLE_PROFILER_SPS
bool currentValue;
GetRecordProfileTimelineMarkers(¤tValue);
if (currentValue != aValue) {
if (aValue) {
++gProfileTimelineRecordingsCount;
- mProfileTimelineStartTime = TimeStamp::Now();
+ mProfileTimelineRecording = true;
} else {
--gProfileTimelineRecordingsCount;
- mProfileTimelineStartTime = TimeStamp();
+ mProfileTimelineRecording = false;
ClearProfileTimelineMarkers();
}
}
return NS_OK;
#else
return NS_ERROR_FAILURE;
#endif
}
NS_IMETHODIMP
nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue)
{
- *aValue = !mProfileTimelineStartTime.IsNull();
+ *aValue = mProfileTimelineRecording;
return NS_OK;
}
nsresult
nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
JS::MutableHandle<JS::Value> aProfileTimelineMarkers)
{
#ifdef MOZ_ENABLE_PROFILER_SPS
@@ -2934,45 +2934,49 @@ nsDocShell::PopProfileTimelineMarkers(JS
mProfileTimelineMarkers.SwapElements(keptMarkers);
return NS_OK;
#else
return NS_ERROR_FAILURE;
#endif
}
-float
-nsDocShell::GetProfileTimelineDelta()
-{
- return (TimeStamp::Now() - mProfileTimelineStartTime).ToMilliseconds();
+nsresult
+nsDocShell::Now(DOMHighResTimeStamp* aWhen)
+{
+ bool ignore;
+ *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds();
+ return NS_OK;
}
void
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(
new InternalProfileTimelineMarker(aName, payload, delta));
}
#endif
}
void
nsDocShell::AddProfileTimelineMarker(const char* aName,
ProfilerBacktrace* aCause,
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);
mProfileTimelineMarkers.AppendElement(
new InternalProfileTimelineMarker(aName, payload, delta));
}
#endif
}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -946,46 +946,42 @@ private:
nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
nsTObserverArray<nsWeakPtr> mPrivacyObservers;
nsTObserverArray<nsWeakPtr> mReflowObservers;
nsTObserverArray<nsWeakPtr> mScrollObservers;
nsCString mOriginalUriString;
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)
{}
~InternalProfileTimelineMarker()
{
delete mPayload;
}
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();
// Separate function to do the actual name (i.e. not _top, _self etc.)
// searching for FindItemWithName.
nsresult DoFindItemWithName(const char16_t* aName,
nsISupports* aRequestor,
nsIDocShellTreeItem* aOriginalRequestor,
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -49,17 +49,17 @@ interface nsIWebBrowserPrint;
interface nsIVariant;
interface nsIPrivacyTransitionObserver;
interface nsIReflowObserver;
interface nsIScrollObserver;
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
{
/**
* Loads a given URI. This will give priority to loading the requested URI
* in the object implementing this interface. If it can't be loaded here
* however, the URL dispatcher will go through its normal process of content
* loading.
*
@@ -668,16 +668,24 @@ interface nsIDocShell : nsIDocShellTreeI
out nsIPrincipal parentCharsetPrincipal);
/**
* Whether the docShell records profile timeline markers at the moment
*/
[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
*/
[implicit_jscontext]
jsval popProfileTimelineMarkers();
/**
* Add an observer to the list of parties to be notified when this docshell's
* private browsing status is changed. |obs| must support weak references.
--- a/dom/webidl/ProfileTimelineMarker.webidl
+++ b/dom/webidl/ProfileTimelineMarker.webidl
@@ -1,11 +1,11 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
dictionary ProfileTimelineMarker {
DOMString name = "";
- DOMTimeStamp start = 0;
- DOMTimeStamp end = 0;
+ DOMHighResTimeStamp start = 0;
+ DOMHighResTimeStamp end = 0;
};
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -16,20 +16,20 @@ import java.util.Locale;
import java.util.Vector;
import org.json.JSONException;
import org.json.JSONObject;
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;
import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.LoadFaviconTask;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.favicons.decoders.IconDirectoryEntry;
@@ -141,20 +141,16 @@ public class BrowserApp extends GeckoApp
OnUrlOpenListener,
OnUrlOpenInBackgroundListener,
ActionModeCompat.Presenter,
LayoutInflater.Factory {
private static final String LOGTAG = "GeckoBrowserApp";
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";
private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
private static final String BROWSER_SEARCH_TAG = "browser_search";
// Request ID for startActivityForResult.
@@ -224,16 +220,18 @@ public class BrowserApp extends GeckoApp
private SharedPreferencesHelper mSharedPreferencesHelper;
private OrderedBroadcastHelper mOrderedBroadcastHelper;
private BroadcastReceiver mOnboardingReceiver;
private BrowserHealthReporter mBrowserHealthReporter;
+ private ReadingListHelper mReadingListHelper;
+
private SystemBarTintManager mTintManager;
// The tab to be selected on editing mode exit.
private Integer mTargetTabForEditingMode;
// The animator used to toggle HomePager visibility has a race where if the HomePager is shown
// (starting the animation), the HomePager is hidden, and the HomePager animation completes,
// both the web content and the HomePager will be hidden. This flag is used to prevent the
@@ -428,100 +426,16 @@ public class BrowserApp extends GeckoApp
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (AndroidGamepadManager.handleKeyEvent(event)) {
return true;
}
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");
final Intent intent = getIntent();
final GeckoProfile p = GeckoProfile.get(this);
if (p != null && !p.inGuestMode()) {
// This is *only* valid because we never want to use the guest mode
@@ -595,49 +509,46 @@ public class BrowserApp extends GeckoApp
setBrowserToolbarListeners();
mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
"Menu:Update",
- "Reader:Added",
- "Reader:FaviconRequest",
"Search:Keyword",
"Prompt:ShowTop",
"Accounts:Exist");
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
"Accounts:Create",
"CharEncoding:Data",
"CharEncoding:State",
"Feedback:LastUrl",
"Feedback:MaybeLater",
"Feedback:OpenPlayStore",
"Menu:Add",
"Menu:Remove",
- "Reader:ListStatusRequest",
- "Reader:Removed",
"Reader:Share",
"Settings:Show",
"Telemetry:Gather",
"Updater:Launch",
"BrowserToolbar:Visibility");
Distribution distribution = Distribution.init(this);
// Init suggested sites engine in BrowserDB.
final SuggestedSites suggestedSites = new SuggestedSites(appContext, distribution);
BrowserDB.setSuggestedSites(suggestedSites);
JavaAddonManager.getInstance().init(appContext);
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);
if (nfc != null) {
nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
@Override
public NdefMessage createNdefMessage(NfcEvent event) {
Tab tab = Tabs.getInstance().getSelectedTab();
@@ -1137,35 +1048,36 @@ public class BrowserApp extends GeckoApp
mOrderedBroadcastHelper = null;
}
if (mBrowserHealthReporter != null) {
mBrowserHealthReporter.uninit();
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");
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener)this,
"Accounts:Create",
"CharEncoding:Data",
"CharEncoding:State",
"Feedback:LastUrl",
"Feedback:MaybeLater",
"Feedback:OpenPlayStore",
"Menu:Add",
"Menu:Remove",
- "Reader:ListStatusRequest",
- "Reader:Removed",
"Reader:Share",
"Settings:Show",
"Telemetry:Gather",
"Updater:Launch",
"BrowserToolbar:Visibility");
if (AppConstants.MOZ_ANDROID_BEAM) {
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
@@ -1514,23 +1426,16 @@ public class BrowserApp extends GeckoApp
final int id = message.getInt("id") + ADDON_MENU_OFFSET;
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
removeAddonMenuItem(id);
}
});
- } 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");
GeckoAppShell.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title);
} else if ("Settings:Show".equals(event)) {
final String resource =
message.optString(GeckoPreferences.INTENT_EXTRA_RESOURCES, null);
@@ -1635,22 +1540,16 @@ public class BrowserApp extends GeckoApp
}
});
// Display notification for Mozilla data reporting, if data should be collected.
if (AppConstants.MOZ_DATA_REPORTING) {
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")) {
// Bring this activity to front so the prompt is visible..
Intent bringToFrontIntent = new Intent();
bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.BROWSER_INTENT_CLASS_NAME);
bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(bringToFrontIntent);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -1830,17 +1830,17 @@ public class GeckoAppShell
if (AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
final File f = new File(aFile);
final DownloadManager dm = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
dm.addCompletedDownload(f.getName(),
f.getName(),
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();
GeckoMediaScannerClient.startScan(context, aFile, mimeType);
}
}
@WrapElementForJNI(stubName = "GetIconForExtensionWrapper")
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/ReadingListHelper.java
@@ -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();
+ }
+ });
+ }
+}
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -77,17 +77,17 @@ public class LocalBrowserDB {
}
private final String mProfile;
// Map of folder GUIDs to IDs. Used for caching.
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;
private final Uri mHistoryUriWithProfile;
private final Uri mHistoryExpireUriWithProfile;
private final Uri mCombinedUriWithProfile;
private final Uri mUpdateHistoryUriWithProfile;
private final Uri mFaviconsUriWithProfile;
@@ -754,22 +754,23 @@ public class LocalBrowserDB {
Bookmarks.PARENT + " = ? OR " +
Bookmarks.PARENT + " = ?",
new String[] { String.valueOf(getFolderIdFromGuid(cr, Bookmarks.TOOLBAR_FOLDER_GUID)),
String.valueOf(getFolderIdFromGuid(cr, Bookmarks.MENU_FOLDER_GUID)),
String.valueOf(getFolderIdFromGuid(cr, Bookmarks.UNFILED_FOLDER_GUID)) },
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
public boolean isBookmark(ContentResolver cr, String uri) {
final Cursor c = cr.query(bookmarksUriWithLimit(1),
new String[] { Bookmarks._ID },
Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ?",
new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) },
--- a/mobile/android/base/menu/MenuItemActionBar.java
+++ b/mobile/android/base/menu/MenuItemActionBar.java
@@ -1,23 +1,24 @@
/* 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.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";
public MenuItemActionBar(Context context) {
this(context, null);
}
public MenuItemActionBar(Context context, AttributeSet attrs) {
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -363,16 +363,17 @@ gbjar.sources += [
'prompts/IntentHandler.java',
'prompts/Prompt.java',
'prompts/PromptInput.java',
'prompts/PromptListAdapter.java',
'prompts/PromptListItem.java',
'prompts/PromptService.java',
'prompts/TabInput.java',
'ReaderModeUtils.java',
+ 'ReadingListHelper.java',
'RemoteClientsDialogFragment.java',
'RemoteTabsExpandableListAdapter.java',
'Restarter.java',
'RestrictedProfiles.java',
'ScrollAnimator.java',
'ServiceNotificationClient.java',
'SessionParser.java',
'SharedPreferencesHelper.java',
--- a/mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_action_bar_button.xml
+++ b/mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_action_bar_button.xml
@@ -1,14 +1,47 @@
<?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/. -->
-<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">
<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">
rename from mobile/android/base/resources/drawable-large-v11/new_tablet_site_security_level.xml
rename to mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_site_security_level.xml
rename from mobile/android/base/resources/drawable-large-v11/new_tablet_site_security_unknown.xml
rename to mobile/android/base/newtablet/res/drawable-large-v11/new_tablet_site_security_unknown.xml
--- a/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_browser_toolbar.xml
+++ b/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_browser_toolbar.xml
@@ -6,50 +6,49 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">
<ImageView android:id="@+id/url_bar_entry"
android:layout_width="match_parent"
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"
android:clickable="false"
android:focusable="false"
android:background="@drawable/url_bar_entry"/>
<org.mozilla.gecko.toolbar.ForwardButton style="@style/UrlBar.ImageButton.Forward.NewTablet"
android:id="@+id/forward"
android:layout_alignLeft="@id/back"/>
<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"
android:src="@drawable/new_tablet_ic_menu_back"
android:contentDescription="@string/back"
android:background="@drawable/new_tablet_url_bar_nav_button"/>
<org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
style="@style/UrlBar.Button"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:visibility="gone"
android:orientation="horizontal"
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"
android:layout_toLeftOf="@id/menu_items"
android:paddingRight="4dip"/>
<LinearLayout android:id="@+id/menu_items"
android:layout_width="wrap_content"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable/new_tablet_site_security_level.xml
@@ -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"/>
--- a/mobile/android/base/resources/values-large-v11/dimens.xml
+++ b/mobile/android/base/resources/values-large-v11/dimens.xml
@@ -3,16 +3,12 @@
- 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/. -->
<resources>
<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>
</resources>
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -20,25 +20,40 @@
<item name="android:layout_centerVertical">true</item>
<item name="android:src">@drawable/ic_menu_forward</item>
<item name="android:background">@drawable/url_bar_nav_button</item>
<!-- Start with the button hidden -->
<item name="android:alpha">0</item>
<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>
<item name="android:layout_marginTop">11.5dp</item>
<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">
<item name="android:background">@drawable/new_tablet_tabs_count</item>
<!-- From UrlBar.ImageButton.NewTablet because we can't inherit directly. -->
<item name="android:layout_width">@dimen/new_tablet_browser_toolbar_menu_item_width</item>
</style>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -12,21 +12,25 @@
<dimen name="browser_toolbar_button_padding">12dp</dimen>
<dimen name="browser_toolbar_icon_width">48dp</dimen>
<dimen name="browser_toolbar_site_security_width">12dp</dimen>
<!-- favicon_size includes 4dp of right padding. We can't use margin (which would allow us to
specify the actual size) because that would decrease the size of our hit target. -->
<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. -->
<dimen name="new_tablet_site_security_padding_vertical">21dp</dimen>
<dimen name="new_tablet_site_security_padding_horizontal">8dp</dimen>
<dimen name="new_tablet_site_security_right_margin">1dp</dimen>
<dimen name="new_tablet_browser_toolbar_height">60dp</dimen>
<dimen name="new_tablet_browser_toolbar_menu_item_width">56dp</dimen>
--- a/mobile/android/base/toolbar/BrowserToolbarNewTablet.java
+++ b/mobile/android/base/toolbar/BrowserToolbarNewTablet.java
@@ -5,36 +5,38 @@
package org.mozilla.gecko.toolbar;
import org.mozilla.gecko.R;
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;
/**
* A toolbar implementation for the tablet redesign (bug 1014156).
* Expected to replace BrowserToolbarTablet once complete.
*/
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
public boolean isAnimating() {
return false;
}
@Override
@@ -44,35 +46,34 @@ class BrowserToolbarNewTablet extends Br
@Override
protected void triggerStopEditingTransition() {
hideUrlEditLayout();
}
@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 =
(MarginLayoutParams) urlDisplayLayout.getLayoutParams();
layoutParams.leftMargin = 0;
// Do the same on the URL edit container
layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
@@ -82,52 +83,49 @@ class BrowserToolbarNewTablet extends Br
// Note, we already translated the favicon, site security, and text field
// in prepareForwardAnimation, so they should appear to have not moved at
// all at this point.
}
}
@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();
}
private void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
if (animation == ForwardButtonAnimation.HIDE) {
anim.attach(forwardButton,
PropertyAnimator.Property.TRANSLATION_X,
-width);
anim.attach(forwardButton,
PropertyAnimator.Property.ALPHA,
0);
} else {
anim.attach(forwardButton,
PropertyAnimator.Property.TRANSLATION_X,
- width);
+ 0);
anim.attach(forwardButton,
PropertyAnimator.Property.ALPHA,
1);
}
urlDisplayLayout.prepareForwardAnimation(anim, animation, width);
}
--- a/mobile/android/base/toolbar/BrowserToolbarTabletBase.java
+++ b/mobile/android/base/toolbar/BrowserToolbarTabletBase.java
@@ -5,16 +5,17 @@
package org.mozilla.gecko.toolbar;
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;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -116,18 +117,23 @@ abstract class BrowserToolbarTabletBase
super.setNextFocusDownId(nextId);
backButton.setNextFocusDownId(nextId);
forwardButton.setNextFocusDownId(nextId);
}
@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) {
return (tab.canDoBack() && !isEditing());
}
protected boolean canDoForward(final Tab tab) {
return (tab.canDoForward() && !isEditing());
--- a/mobile/android/base/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/toolbar/ToolbarDisplayLayout.java
@@ -530,16 +530,18 @@ public class ToolbarDisplayLayout extend
return mSiteSecurity;
}
}
void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
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);
anim.attach(mFavicon,
PropertyAnimator.Property.TRANSLATION_X,
0);
anim.attach(mSiteSecurity,
PropertyAnimator.Property.TRANSLATION_X,
--- a/mobile/android/base/widget/ThemedEditText.java.in
+++ b/mobile/android/base/widget/ThemedEditText.java.in
@@ -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
--- a/mobile/android/base/widget/ThemedImageButton.java.in
+++ b/mobile/android/base/widget/ThemedImageButton.java.in
@@ -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
--- a/mobile/android/base/widget/ThemedImageView.java.in
+++ b/mobile/android/base/widget/ThemedImageView.java.in
@@ -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
--- a/mobile/android/base/widget/ThemedRelativeLayout.java.in
+++ b/mobile/android/base/widget/ThemedRelativeLayout.java.in
@@ -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
--- a/mobile/android/base/widget/ThemedTextView.java.in
+++ b/mobile/android/base/widget/ThemedTextView.java.in
@@ -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
--- a/mobile/android/base/widget/ThemedView.java.frag
+++ b/mobile/android/base/widget/ThemedView.java.frag
@@ -10,36 +10,47 @@ import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
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 };
private static final int[] STATE_DARK = { R.attr.state_dark };
protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
private boolean mIsPrivate;
private boolean mIsLight;
private boolean mIsDark;
private boolean mAutoUpdateTheme = true;
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();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
--- a/mobile/android/base/widget/ThemedView.java.in
+++ b/mobile/android/base/widget/ThemedView.java.in
@@ -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
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/Reporter.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/Reporter.java
@@ -137,30 +137,37 @@ public final class Reporter extends Broa
private void putWifiResults(List<ScanResult> results) {
if (mBundle == null) {
return;
}
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);
}
}
}
private void putCellResults(List<CellInfo> cells) {
if (mBundle == null) {
return;
}
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);
}
}
}
private void reportCollectedLocation() {
@@ -186,17 +193,19 @@ public final class Reporter extends Broa
Log.w(LOG_TAG, "Failed to convert bundle to JSON: " + e);
return;
}
if (AppGlobals.isDebug) {
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);
} catch (IOException e) {
Log.w(LOG_TAG, e.toString());
}
}
}
--- a/toolkit/components/search/SearchSuggestionController.jsm
+++ b/toolkit/components/search/SearchSuggestionController.jsm
@@ -42,17 +42,18 @@ Services.prefs.addObserver(BROWSER_SUGGE
* @constructor
*/
this.SearchSuggestionController = function SearchSuggestionController(callback = null) {
this._callback = 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,
/**
* The maximum number of remote search engine results to return.
*/
maxRemoteResults: 10,
@@ -195,18 +196,17 @@ this.SearchSuggestionController.prototyp
switch (result.searchResult) {
case Ci.nsIAutoCompleteResult.RESULT_SUCCESS:
case Ci.nsIAutoCompleteResult.RESULT_NOMATCH:
if (result.searchString !== this._searchString) {
deferredFormHistory.resolve("Unexpected response, this._searchString does not match form history response");
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({
result: fhEntries,
formHistoryResult: result,
});
break;
case Ci.nsIAutoCompleteResult.RESULT_FAILURE:
@@ -330,18 +330,23 @@ this.SearchSuggestionController.prototyp
} else if (result.formHistoryResult) { // Local results have a formHistoryResult property.
results.formHistoryResult = result.formHistoryResult;
results.local = result.result || [];
} else { // Remote result
results.remote = result.result || [];
}
}
+ // 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];
let dupIndex = results.remote.indexOf(term);
if (dupIndex != -1) {
results.remote.splice(dupIndex, 1);
}
}
--- a/toolkit/components/search/nsSearchSuggestions.js
+++ b/toolkit/components/search/nsSearchSuggestions.js
@@ -20,16 +20,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
*/
function SuggestAutoComplete() {
this._init();
}
SuggestAutoComplete.prototype = {
_init: function() {
this._suggestionController = new SearchSuggestionController(obj => this.onResultsReturned(obj));
+ this._suggestionController.maxLocalResults = this._historyLimit;
},
get _suggestionLabel() {
delete this._suggestionLabel;
let bundle = Services.strings.createBundle("chrome://global/locale/search/search.properties");
return this._suggestionLabel = bundle.GetStringFromName("suggestion_label");
},
@@ -52,18 +53,17 @@ SuggestAutoComplete.prototype = {
* Callback for handling results from SearchSuggestionController.jsm
* @private
*/
onResultsReturned: function(results) {
let finalResults = [];
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("");
}
// If there are remote matches, add them.
if (results.remote.length) {
// "comments" column values for suggestions starts as empty strings
let comments = new Array(results.remote.length).fill("", 1);
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
@@ -144,17 +144,16 @@ add_task(function* simple_non_ascii() {
let result = yield controller.fetch("I ❤️", false, getEngine);
do_check_eq(result.term, "I ❤️");
do_check_eq(result.local.length, 1);
do_check_eq(result.local[0], "I ❤️ XUL");
do_check_eq(result.remote.length, 1);
do_check_eq(result.remote[0], "I ❤️ Mozilla");
});
-
add_task(function* both_local_remote_result_dedupe() {
yield updateSearchHistory("bump", "Mozilla");
let controller = new SearchSuggestionController();
let result = yield controller.fetch("mo", false, getEngine);
do_check_eq(result.term, "mo");
do_check_eq(result.local.length, 1);
do_check_eq(result.local[0], "Mozilla");
@@ -264,16 +263,46 @@ add_task(function* both_identical_with_m
}
do_check_eq(result.remote.length, 10);
for (let i = 0; i < controller.maxRemoteResults; i++) {
do_check_eq(result.remote[i],
"letter " + String.fromCharCode("A".charCodeAt() + controller.maxLocalResults + i));
}
});
+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;
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");
@@ -283,31 +312,35 @@ add_task(function* one_of_each() {
add_task(function* local_result_returned_remote_result_disabled() {
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
let controller = new SearchSuggestionController();
controller.maxLocalResults = 1;
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);
});
add_task(function* local_result_returned_remote_result_disabled_after_creation_of_controller() {
let controller = new SearchSuggestionController();
controller.maxLocalResults = 1;
controller.maxRemoteResults = 1;
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);
});
add_task(function* one_of_each_disabled_before_creation_enabled_after_creation_of_controller() {
Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
let controller = new SearchSuggestionController();
controller.maxLocalResults = 1;
@@ -326,18 +359,20 @@ add_task(function* reset_suggestions_pre
});
add_task(function* one_local_zero_remote() {
let controller = new SearchSuggestionController();
controller.maxLocalResults = 1;
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);
});
add_task(function* zero_local_one_remote() {
let controller = new SearchSuggestionController();
controller.maxLocalResults = 0;
controller.maxRemoteResults = 1;
let result = yield controller.fetch("letter ", false, getEngine);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/thumbnails/PageThumbUtils.jsm
@@ -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];
+ }
+};
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -5,38 +5,34 @@
"use strict";
this.EXPORTED_SYMBOLS = ["PageThumbs", "PageThumbsStorage"];
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.
const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
/**
* Name of the directory in the profile that contains the thumbnails.
*/
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);
Cu.import("resource://gre/modules/osfile.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
@@ -64,16 +60,18 @@ XPCOMUtils.defineLazyGetter(this, "gUnic
});
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
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
*/
const TaskUtils = {
/**
* Read the bytes from a blob, asynchronously.
*
@@ -163,123 +161,195 @@ this.PageThumbs = {
* @param aUrl The web page's url.
* @return The path of the thumbnail file.
*/
getThumbnailPath: function PageThumbs_getThumbnailPath(aUrl) {
return PageThumbsStorage.getFilePathForURL(aUrl);
},
/**
- * 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 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.
ctx.save();
ctx.scale(scale, scale);
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();
- 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.
* @param aCallback The function to be called when finished (optional).
*/
captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) {
if (!this._prefEnabled()) {
return;
}
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) {
aCallback(isSuccess);
}
}).bind(this));
},
@@ -370,84 +440,23 @@ this.PageThumbs = {
* Unregister an expiration filter.
* @param aFilter A filter that was previously passed to addExpirationFilter.
*/
removeExpirationFilter: function PageThumbs_removeExpirationFilter(aFilter) {
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
* canvas. If not given, the hidden window will be used.
* @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);
},
/**
* Given a channel, returns true if it should be considered an "error
* response", false otherwise.
*/
_isChannelErrorResponse: function(channel) {
// No valid document channel sounds like an error to me!
--- a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
+++ b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
@@ -1,19 +1,17 @@
/* 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/. */
-(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");
const STATE_LOADING = 1;
const STATE_CAPTURING = 2;
const STATE_CANCELED = 3;
const backgroundPageThumbsContent = {
@@ -44,17 +42,17 @@ const backgroundPageThumbsContent = {
addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
},
observe: function (subj, topic, data) {
// Arrange to prevent (most) popup dialogs for this window - popups done
// 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).
disableDialogs();
}
},
get _webNav() {
@@ -124,19 +122,29 @@ const backgroundPageThumbsContent = {
}
},
_captureCurrentPage: function () {
let capture = this._currentCapture;
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 => {
capture.imageBlob = new Blob([blob]);
// Load about:blank to finish the capture and wait for onStateChange.
this._loadAboutBlank();
});
},
@@ -179,10 +187,8 @@ const backgroundPageThumbsContent = {
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsIObserver,
]),
};
backgroundPageThumbsContent.init();
-
-})();
--- a/toolkit/components/thumbnails/moz.build
+++ b/toolkit/components/thumbnails/moz.build
@@ -10,11 +10,12 @@ EXTRA_COMPONENTS += [
'BrowserPageThumbs.manifest',
'PageThumbsProtocol.js',
]
EXTRA_JS_MODULES += [
'BackgroundPageThumbs.jsm',
'PageThumbs.jsm',
'PageThumbsWorker.js',
+ 'PageThumbUtils.jsm',
]
JAR_MANIFESTS += ['jar.mn']
--- a/toolkit/components/thumbnails/test/browser.ini
+++ b/toolkit/components/thumbnails/test/browser.ini
@@ -1,40 +1,46 @@
[DEFAULT]
-skip-if = e10s # Bug 863512 - thumbnails are disabled with e10s enabled.
support-files =
background_red.html
background_red_redirect.sjs
background_red_scroll.html
head.js
privacy_cache_control.sjs
thumbnails_background.sjs
thumbnails_crash_content_helper.js
thumbnails_update.sjs
[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]
[browser_thumbnails_bg_no_duplicates.js]
[browser_thumbnails_bg_captureIfMissing.js]
[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
--- a/toolkit/components/thumbnails/test/head.js
+++ b/toolkit/components/thumbnails/test/head.js
@@ -132,16 +132,17 @@ function captureAndCheckColor(aRed, aGre
next();
});
});
}
/**
* 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.
*/
function retrieveImageDataForURL(aURL, aCallback) {
let width = 100, height = 100;
let thumb = PageThumbs.getThumbnailURL(aURL, width, height);
// create a tab with a chrome:// URL so it can host the thumbnail image.
// Note that we tried creating the element directly in the top-level chrome
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -6,16 +6,19 @@ let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
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",
"nsICrashReporter");
#endif
let FocusSyncHandler = {
init: function() {
@@ -363,16 +366,47 @@ addEventListener("ZoomChangeUsingMouseWh
sendAsyncMessage("ZoomChangeUsingMouseWheel", {});
}, false);
addMessageListener("UpdateCharacterSet", function (aMessage) {
docShell.charset = aMessage.data.value;
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;
if (Services.appinfo.browserTabsRemoteAutostart) {
// Currently, the addon shims are only supported when autostarting
// with remote tabs.
AddonsChild = RemoteAddonsChild.init(this);
--- a/toolkit/devtools/server/actors/timeline.js
+++ b/toolkit/devtools/server/actors/timeline.js
@@ -46,17 +46,18 @@ let TimelineActor = exports.TimelineActo
* at most, when profile markers are found. A marker has the following
* properties:
* - start {Number} ms
* - end {Number} ms
* - name {String}
*/
"markers" : {
type: "markers",
- markers: Arg(0, "array:json")
+ markers: Arg(0, "array:json"),
+ endTime: Arg(1, "number")
},
/**
* "memory" events emitted in tandem with "markers", if this was enabled
* when the recording started.
*/
"memory" : {
type: "memory",
@@ -75,16 +76,17 @@ let TimelineActor = exports.TimelineActo
}
},
initialize: function(conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn);
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);
events.on(this.tabActor, "window-ready", this._onWindowReady);
},
/**
* The timeline actor is the first (and last) in its hierarchy to use protocol.js
@@ -137,17 +139,18 @@ let TimelineActor = exports.TimelineActo
return;
}
let markers = [];
for (let docShell of this.docShells) {
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());
}
if (this._framerateActor) {
events.emit(this, "ticks", Date.now(), this._framerateActor.getPendingTicks());
}
@@ -171,35 +174,40 @@ let TimelineActor = exports.TimelineActo
/**
* Start recording profile markers.
*/
start: method(function({ withMemory, withTicks }) {
if (this._isRecording) {
return;
}
this._isRecording = true;
+ this._startTime = this.docShells[0].now();
for (let docShell of this.docShells) {
docShell.recordProfileTimelineMarkers = true;
}
if (withMemory) {
this._memoryActor = new MemoryActor(this.conn, this.tabActor);
events.emit(this, "memory", Date.now(), this._memoryActor.measure());
}
if (withTicks) {
this._framerateActor = new FramerateActor(this.conn, this.tabActor);
this._framerateActor.startRecording();
}
this._pullTimelineData();
+ return this._startTime;
}, {
request: {
withMemory: Option(0, "boolean"),
withTicks: Option(0, "boolean")
+ },
+ response: {
+ value: RetVal("number")
}
}),
/**
* Stop recording profile markers.
*/
stop: method(function() {
if (!this._isRecording) {
@@ -223,18 +231,16 @@ let TimelineActor = exports.TimelineActo
}, {}),
/**
* When a new window becomes available in the tabActor, start recording its
* markers if we were recording.
*/
_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);
docShell.recordProfileTimelineMarkers = true;
}
}
});
--- a/toolkit/devtools/server/actors/webaudio.js
+++ b/toolkit/devtools/server/actors/webaudio.js
@@ -288,16 +288,46 @@ let AudioNodeActor = exports.AudioNodeAc
let props = Object.keys(NODE_PROPERTIES[this.type]);
return props.map(prop =>
({ param: prop, value: this.getParam(prop), flags: this.getParamFlags(prop) }));
}, {
response: { params: RetVal("json") }
}),
/**
+ * 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)`.
*/
connectNode: method(function (destActor, output, input) {
let srcNode = this.node.get();
let destNode = destActor.node.get();
if (srcNode === null || destNode === null) {
return CollectedAudioNodeError();
--- a/toolkit/modules/secondscreen/SimpleServiceDiscovery.jsm
+++ b/toolkit/modules/secondscreen/SimpleServiceDiscovery.jsm
@@ -130,17 +130,19 @@ var SimpleServiceDiscovery = {
// Stop the current continuous search
stopSearch: function stopSearch() {
this._searchRepeat.cancel();
},
_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() {
// If a search is already active, shut it down.
this._searchShutdown();
// We only search if on local network
if (!this._usingLAN()) {
--- a/toolkit/themes/linux/global/toolbar.css
+++ b/toolkit/themes/linux/global/toolbar.css
@@ -29,16 +29,17 @@ menubar, toolbar[type="menubar"] {
min-width: 1px;
min-height: 20px;
padding: 1px 0px;
}
menubar:-moz-lwtheme,
toolbar:-moz-lwtheme {
-moz-appearance: none;
+ color: inherit;
}
/* in browser.xul, the menubar is inside a toolbar... */
toolbaritem > menubar {
-moz-appearance: none;
}
/* ::::: toolbar decorations ::::: */