From bcedd516f7be67b5489959fbf165be677749cb53 Mon Sep 17 00:00:00 2001 From: Gina Yeh Date: Thu, 1 Nov 2012 19:49:07 +0800 Subject: [PATCH 01/43] Bug 806713 - Patch 1: NS_ASSERTION failure in UnixSocket.cpp when create a sco socket, r=qdot --- dom/bluetooth/linux/BluetoothDBusService.cpp | 52 +++----------------- 1 file changed, 7 insertions(+), 45 deletions(-) diff --git a/dom/bluetooth/linux/BluetoothDBusService.cpp b/dom/bluetooth/linux/BluetoothDBusService.cpp index ab5c9704cea..7b412d99be8 100644 --- a/dom/bluetooth/linux/BluetoothDBusService.cpp +++ b/dom/bluetooth/linux/BluetoothDBusService.cpp @@ -2387,45 +2387,6 @@ BluetoothDBusService::Disconnect(const uint16_t aProfileId, DispatchBluetoothReply(aRunnable, v, replyError); } -class CreateBluetoothScoSocket : public nsRunnable -{ -public: - CreateBluetoothScoSocket(UnixSocketConsumer* aConsumer, - const nsAString& aAddress, - bool aAuth, - bool aEncrypt) - : mConsumer(aConsumer), - mAddress(aAddress), - mAuth(aAuth), - mEncrypt(aEncrypt) - { - } - - nsresult - Run() - { - MOZ_ASSERT(!NS_IsMainThread()); - - nsString replyError; - BluetoothUnixSocketConnector* c = - new BluetoothUnixSocketConnector(BluetoothSocketType::SCO, -1, - mAuth, mEncrypt); - - if (!mConsumer->ConnectSocket(c, NS_ConvertUTF16toUTF8(mAddress).get())) { - replyError.AssignLiteral("SocketConnectionError"); - return NS_ERROR_FAILURE; - } - - return NS_OK; - } - -private: - nsRefPtr mConsumer; - nsString mAddress; - bool mAuth; - bool mEncrypt; -}; - class ConnectBluetoothSocketRunnable : public nsRunnable { public: @@ -2583,12 +2544,13 @@ BluetoothDBusService::GetScoSocket(const nsAString& aAddress, return NS_ERROR_FAILURE; } - nsRefPtr func(new CreateBluetoothScoSocket(aConsumer, - aAddress, - aAuth, - aEncrypt)); - if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) { - NS_WARNING("Cannot dispatch firmware loading task!"); + nsString replyError; + BluetoothUnixSocketConnector* c = + new BluetoothUnixSocketConnector(BluetoothSocketType::SCO, -1, + aAuth, aEncrypt); + + if (!aConsumer->ConnectSocket(c, NS_ConvertUTF16toUTF8(aAddress).get())) { + replyError.AssignLiteral("SocketConnectionError"); return NS_ERROR_FAILURE; } From 52160f6a8fda9d30917ec81620c0f030d02bfb35 Mon Sep 17 00:00:00 2001 From: Patrick McManus Date: Thu, 1 Nov 2012 08:47:29 -0400 Subject: [PATCH 02/43] bug 806200 - process gzip responses without considering accept-encoding r=honzab --- netwerk/protocol/http/nsHttpHandler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index d1f7652f504..cbda071ba1b 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -405,6 +405,11 @@ nsHttpHandler::IsAcceptableEncoding(const char *enc) if (!PL_strncasecmp(enc, "x-", 2)) enc += 2; + // gzip and deflate are inherently acceptable in modern HTTP - always + // process them if a stream converter can also be found. + if (!PL_strcasecmp(enc, "gzip") || !PL_strcasecmp(enc, "deflate")) + return true; + return nsHttp::FindToken(mAcceptEncodings.get(), enc, HTTP_LWS ",") != nullptr; } From bceb631bc5fb601ae40c0aed3ca41ae195a5e9c6 Mon Sep 17 00:00:00 2001 From: Josh Aas Date: Thu, 1 Nov 2012 09:19:24 -0400 Subject: [PATCH 03/43] Back out both patches for bug 647216 due to regression documented in bug 806244. --- dom/base/nsDOMWindowUtils.cpp | 19 +- dom/base/nsDOMWindowUtils.h | 3 +- dom/interfaces/base/nsIDOMWindowUtils.idl | 25 ++- dom/ipc/TabChild.cpp | 3 +- .../mochitest/tests/SimpleTest/EventUtils.js | 9 +- toolkit/content/WindowDraggingUtils.jsm | 10 +- .../content/tests/chrome/window_titlebar.xul | 27 ++- widget/cocoa/nsChildView.h | 8 - widget/cocoa/nsChildView.mm | 55 ++---- widget/cocoa/nsCocoaWindow.h | 3 - widget/cocoa/nsCocoaWindow.mm | 172 +----------------- 11 files changed, 59 insertions(+), 275 deletions(-) diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index d493cb7b675..b38d6b89fbc 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -511,12 +511,11 @@ nsDOMWindowUtils::SendMouseEvent(const nsAString& aType, int32_t aModifiers, bool aIgnoreRootScrollFrame, float aPressure, - unsigned short aInputSourceArg, - bool *aPreventDefault) + unsigned short aInputSourceArg) { return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers, aIgnoreRootScrollFrame, aPressure, - aInputSourceArg, false, aPreventDefault); + aInputSourceArg, false); } NS_IMETHODIMP @@ -533,7 +532,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(const nsAString& aType, SAMPLE_LABEL("nsDOMWindowUtils", "SendMouseEventToWindow"); return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers, aIgnoreRootScrollFrame, aPressure, - aInputSourceArg, true, nullptr); + aInputSourceArg, true); } static nsIntPoint @@ -556,8 +555,7 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType, bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, - bool aToWindow, - bool *aPreventDefault) + bool aToWindow) { if (!nsContentUtils::IsCallerChrome()) { return NS_ERROR_DOM_SECURITY_ERR; @@ -584,9 +582,7 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType, else if (aType.EqualsLiteral("contextmenu")) { msg = NS_CONTEXTMENU; contextMenuKey = (aButton == 0); - } else if (aType.EqualsLiteral("MozMouseHittest")) - msg = NS_MOUSE_MOZHITTEST; - else + } else return NS_ERROR_FAILURE; if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) { @@ -627,10 +623,7 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType, status = nsEventStatus_eIgnore; return presShell->HandleEvent(view->GetFrame(), &event, false, &status); } - nsresult rv = widget->DispatchEvent(&event, status); - *aPreventDefault = (status == nsEventStatus_eConsumeNoDefault); - - return rv; + return widget->DispatchEvent(&event, status); } NS_IMETHODIMP diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h index 2db86206dae..f913b74b76a 100644 --- a/dom/base/nsDOMWindowUtils.h +++ b/dom/base/nsDOMWindowUtils.h @@ -44,8 +44,7 @@ protected: bool aIgnoreRootScrollFrame, float aPressure, unsigned short aInputSourceArg, - bool aToWindow, - bool *aPreventDefault); + bool aToWindow); static mozilla::widget::Modifiers GetWidgetModifiers(int32_t aModifiers); }; diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 72b1cd04a31..0644850e010 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -40,7 +40,7 @@ interface nsIDOMTouch; interface nsIDOMClientRect; interface nsIURI; -[scriptable, uuid(b3a3589d-cc9d-4123-9b21-51c66e88b436)] +[scriptable, uuid(C98B7275-93C4-4EAD-B7CF-573D872C1071)] interface nsIDOMWindowUtils : nsISupports { /** @@ -204,8 +204,7 @@ interface nsIDOMWindowUtils : nsISupports { const long MODIFIER_OS = 0x0400; /** Synthesize a mouse event. The event types supported are: - * mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu, - * MozMouseHitTest + * mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu * * Events are sent in coordinates offset by aX and aY from the window. * @@ -237,18 +236,16 @@ interface nsIDOMWindowUtils : nsISupports { * @param aPressure touch input pressure: 0.0 -> 1.0 * @param aInputSourceArg input source, see nsIDOMMouseEvent for values, * defaults to mouse input. - * - * returns true if the page called prevent default on this event */ - boolean sendMouseEvent(in AString aType, - in float aX, - in float aY, - in long aButton, - in long aClickCount, - in long aModifiers, - [optional] in boolean aIgnoreRootScrollFrame, - [optional] in float aPressure, - [optional] in unsigned short aInputSourceArg); + void sendMouseEvent(in AString aType, + in float aX, + in float aY, + in long aButton, + in long aClickCount, + in long aModifiers, + [optional] in boolean aIgnoreRootScrollFrame, + [optional] in float aPressure, + [optional] in unsigned short aInputSourceArg); /** Synthesize a touch event. The event types supported are: * touchstart, touchend, touchmove, and touchcancel diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index cbe6353d580..ec1942c0767 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -1276,9 +1276,8 @@ TabChild::RecvMouseEvent(const nsString& aType, { nsCOMPtr utils(GetDOMWindowUtils()); NS_ENSURE_TRUE(utils, true); - bool ignored = false; utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers, - aIgnoreRootScrollFrame, 0, 0, &ignored); + aIgnoreRootScrollFrame, 0, 0); return true; } diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js index 4b023ddcb37..e45dbd22b16 100644 --- a/testing/mochitest/tests/SimpleTest/EventUtils.js +++ b/testing/mochitest/tests/SimpleTest/EventUtils.js @@ -206,13 +206,11 @@ function _parseModifiers(aEvent) * a mousedown followed by a mouse up is performed. * * aWindow is optional, and defaults to the current window object. - * - * Returns whether the event had preventDefault() called on it. */ function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) { var rect = aTarget.getBoundingClientRect(); - return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, + synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY, aEvent, aWindow); } function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) @@ -236,7 +234,6 @@ function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) function synthesizeMouseAtPoint(left, top, aEvent, aWindow) { var utils = _getDOMWindowUtils(aWindow); - var defaultPrevented = false; if (utils) { var button = aEvent.button || 0; @@ -244,15 +241,13 @@ function synthesizeMouseAtPoint(left, top, aEvent, aWindow) var modifiers = _parseModifiers(aEvent); if (("type" in aEvent) && aEvent.type) { - defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers); + utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers); } else { utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers); utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers); } } - - return defaultPrevented; } function synthesizeTouchAtPoint(left, top, aEvent, aWindow) { diff --git a/toolkit/content/WindowDraggingUtils.jsm b/toolkit/content/WindowDraggingUtils.jsm index 458843c13c7..1220faf4696 100644 --- a/toolkit/content/WindowDraggingUtils.jsm +++ b/toolkit/content/WindowDraggingUtils.jsm @@ -2,18 +2,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/. */ -#ifdef XP_WIN -#define USE_HITTEST -#elifdef MOZ_WIDGET_COCOA -#define USE_HITTEST -#endif - this.EXPORTED_SYMBOLS = [ "WindowDraggingElement" ]; this.WindowDraggingElement = function WindowDraggingElement(elem) { this._elem = elem; this._window = elem.ownerDocument.defaultView; -#ifdef USE_HITTEST +#ifdef XP_WIN if (!this.isPanel()) this._elem.addEventListener("MozMouseHittest", this, false); else @@ -60,7 +54,7 @@ WindowDraggingElement.prototype = { }, handleEvent: function(aEvent) { let isPanel = this.isPanel(); -#ifdef USE_HITTEST +#ifdef XP_WIN if (!isPanel) { if (this.shouldDrag(aEvent)) aEvent.preventDefault(); diff --git a/toolkit/content/tests/chrome/window_titlebar.xul b/toolkit/content/tests/chrome/window_titlebar.xul index 922357d2595..c0405eeb3a3 100644 --- a/toolkit/content/tests/chrome/window_titlebar.xul +++ b/toolkit/content/tests/chrome/window_titlebar.xul @@ -52,6 +52,7 @@ @@ -105,18 +106,26 @@ function test_titlebar() var titlebar = document.getElementById("titlebar"); var label = document.getElementById("label"); - // On Mac, the window can also be moved with the statusbar, but this works - // via the MozMouseHittest event, not via mouse events. + // on Mac, the window can also be moved with the statusbar if (navigator.platform.indexOf("Mac") >= 0) { - var preventDefaulted; - var statuslabel = document.getElementById("statuslabel"); - preventDefaulted = synthesizeMouse(statuslabel, 2, 2, { type: "MozMouseHittest" }); - SimpleTest.ok(preventDefaulted, "MozMouseHittest should have been defaultPrevented over statusbar"); + var statuslabelnodrag = document.getElementById("statuslabelnodrag"); - var button = document.getElementById("button"); - preventDefaulted = synthesizeMouse(button, 2, 2, { type: "MozMouseHittest" }); - SimpleTest.ok(!preventDefaulted, "MozMouseHittest should NOT have been defaultPrevented over button"); + origoldx = window.screenX; + origoldy = window.screenY; + + synthesizeMouse(statuslabel, 2, 2, { type: "mousedown" }); + synthesizeMouse(statuslabel, 22, 22, { type: "mousemove" }); + SimpleTest.is(window.screenX, origoldx + 20, "move window with statusbar horizontal"); + SimpleTest.is(window.screenY, origoldy + 20, "move window with statusbar vertical"); + synthesizeMouse(statuslabel, 22, 22, { type: "mouseup" }); + + // event was cancelled so the drag should not have occurred + synthesizeMouse(statuslabelnodrag, 2, 2, { type: "mousedown" }); + synthesizeMouse(statuslabelnodrag, 22, 22, { type: "mousemove" }); + SimpleTest.is(window.screenX, origoldx + 20, "move window with statusbar cancelled mousedown horizontal"); + SimpleTest.is(window.screenY, origoldy + 20, "move window with statusbar cancelled mousedown vertical"); + synthesizeMouse(statuslabelnodrag, 22, 22, { type: "mouseup" }); } origoldx = window.screenX; diff --git a/widget/cocoa/nsChildView.h b/widget/cocoa/nsChildView.h index 9130eaa6b6c..b969ff86e8d 100644 --- a/widget/cocoa/nsChildView.h +++ b/widget/cocoa/nsChildView.h @@ -183,10 +183,6 @@ typedef NSInteger NSEventGestureAxis; - (NSEventPhase)momentumPhase; @end -@protocol EventRedirection - - (NSView*)targetView; -@end - @interface ChildView : NSView< #ifdef ACCESSIBILITY mozAccessible, @@ -271,8 +267,6 @@ typedef NSInteger NSEventGestureAxis; // class initialization + (void)initialize; -+ (void)registerViewForDraggedTypes:(NSView*)aView; - // these are sent to the first responder when the window key status changes - (void)viewsWindowDidBecomeKey; - (void)viewsWindowDidResignKey; @@ -282,8 +276,6 @@ typedef NSInteger NSEventGestureAxis; - (void)sendFocusEvent:(uint32_t)eventType; -- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent; - - (void)handleMouseMoved:(NSEvent*)aEvent; - (void)drawRect:(NSRect)aRect inTitlebarContext:(CGContextRef)aContext; diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 5ad39c55c65..43d613b3e18 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -1912,20 +1912,6 @@ NSEvent* gLastDragMouseDownEvent = nil; } } -+ (void)registerViewForDraggedTypes:(NSView*)aView -{ - [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, - NSStringPboardType, - NSHTMLPboardType, - NSURLPboardType, - NSFilesPromisePboardType, - kWildcardPboardType, - kCorePboardType_url, - kCorePboardType_urld, - kCorePboardType_urln, - nil]]; -} - // initWithFrame:geckoChild: - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild { @@ -1973,8 +1959,17 @@ NSEvent* gLastDragMouseDownEvent = nil; } // register for things we'll take from other applications - [ChildView registerViewForDraggedTypes:self]; - + PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView initWithFrame: registering drag types\n")); + [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, + NSStringPboardType, + NSHTMLPboardType, + NSURLPboardType, + NSFilesPromisePboardType, + kWildcardPboardType, + kCorePboardType_url, + kCorePboardType_urld, + kCorePboardType_urln, + nil]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowBecameMain:) name:NSWindowDidBecomeMainNotification @@ -2344,7 +2339,7 @@ NSEvent* gLastDragMouseDownEvent = nil; - (BOOL)mouseDownCanMoveWindow { - return [[self window] isMovableByWindowBackground]; + return NO; } - (void)lockFocus @@ -3290,25 +3285,6 @@ NSEvent* gLastDragMouseDownEvent = nil; mGeckoChild->DispatchEvent(&event, status); } -- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent -{ - if (!theEvent || !mGeckoChild) { - return; - } - - nsCocoaWindow* windowWidget = mGeckoChild->GetXULWindowWidget(); - if (!windowWidget) { - return; - } - - // We assume later on that sending a hit test event won't cause widget destruction. - nsMouseEvent hitTestEvent(true, NS_MOUSE_MOZHITTEST, mGeckoChild, nsMouseEvent::eReal); - [self convertCocoaMouseEvent:theEvent toGeckoEvent:&hitTestEvent]; - bool result = mGeckoChild->DispatchWindowEvent(hitTestEvent); - - [windowWidget->GetCocoaWindow() setMovableByWindowBackground:result]; -} - - (void)handleMouseMoved:(NSEvent*)theEvent { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; @@ -4833,11 +4809,6 @@ ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent) NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window); NSView* view = [[[window contentView] superview] hitTest:windowEventLocation]; - - while([view conformsToProtocol:@protocol(EventRedirection)]) { - view = [(id)view targetView]; - } - if (![view isKindOfClass:[ChildView class]]) return nil; @@ -4936,7 +4907,7 @@ ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent, NSWindow *ourWindow = [self window]; NSView *contentView = [ourWindow contentView]; if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView)) - return [ourWindow isMovableByWindowBackground]; + return NO; return [self nsChildView_NSView_mouseDownCanMoveWindow]; } diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h index 381d5ab0168..91b7854f2ce 100644 --- a/widget/cocoa/nsCocoaWindow.h +++ b/widget/cocoa/nsCocoaWindow.h @@ -18,7 +18,6 @@ class nsCocoaWindow; class nsChildView; class nsMenuBarX; -@class ChildView; // Value copied from BITMAP_MAX_AREA, used in nsNativeThemeCocoa.mm #define CUIDRAW_MAX_AREA 500000 @@ -177,7 +176,6 @@ typedef struct _nsCocoaWindowList { TitlebarAndBackgroundColor *mColor; float mUnifiedToolbarHeight; NSColor *mBackgroundColor; - NSView *mTitlebarView; // strong } // Pass nil here to get the default appearance. - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive; @@ -188,7 +186,6 @@ typedef struct _nsCocoaWindowList { - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync; - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect; - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState; -- (ChildView*)mainChildView; @end class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm index b609dcf610f..7461f023082 100644 --- a/widget/cocoa/nsCocoaWindow.mm +++ b/widget/cocoa/nsCocoaWindow.mm @@ -447,10 +447,6 @@ nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect, [mWindow setContentMinSize:NSMakeSize(60, 60)]; [mWindow disableCursorRects]; - // Make sure the window starts out not draggable by the background. - // We will turn it on as necessary. - [mWindow setMovableByWindowBackground:NO]; - [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow]; mWindowMadeHere = true; @@ -2587,94 +2583,7 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; @end -@interface TitlebarMouseHandlingView : NSView -{ - ToolbarWindow* mWindow; // weak - BOOL mProcessingRightMouseDown; -} - -- (id)initWithWindow:(ToolbarWindow*)aWindow; -@end - -@implementation TitlebarMouseHandlingView - -- (id)initWithWindow:(ToolbarWindow*)aWindow -{ - if ((self = [super initWithFrame:[aWindow titlebarRect]])) { - mWindow = aWindow; - [self setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)]; - [ChildView registerViewForDraggedTypes:self]; - mProcessingRightMouseDown = NO; - } - return self; -} - -- (NSView*)targetView -{ - return [mWindow mainChildView]; -} - -- (BOOL)mouseDownCanMoveWindow -{ - return [mWindow isMovableByWindowBackground]; -} - -// We redirect many types of events to the window's mainChildView simply by -// passing the event object to the respective handler method. We don't need any -// coordinate transformations because event coordinates are relative to the -// window. -// We only need to handle event types whose target NSView is determined by the -// event's position. We don't need to handle key events and NSMouseMoved events -// because those are only sent to the window's first responder. This view -// doesn't override acceptsFirstResponder, so it will never receive those kinds -// of events. - -- (void)mouseMoved:(NSEvent*)aEvent { [[self targetView] mouseMoved:aEvent]; } -- (void)mouseDown:(NSEvent*)aEvent { [[self targetView] mouseDown:aEvent]; } -- (void)mouseUp:(NSEvent*)aEvent { [[self targetView] mouseUp:aEvent]; } -- (void)mouseDragged:(NSEvent*)aEvent { [[self targetView] mouseDragged:aEvent]; } -- (void)rightMouseDown:(NSEvent*)aEvent -{ - // To avoid recursion... - if (mProcessingRightMouseDown) - return; - mProcessingRightMouseDown = YES; - [[self targetView] rightMouseDown:aEvent]; - mProcessingRightMouseDown = NO; -} -- (void)rightMouseUp:(NSEvent*)aEvent { [[self targetView] rightMouseUp:aEvent]; } -- (void)rightMouseDragged:(NSEvent*)aEvent { [[self targetView] rightMouseDragged:aEvent]; } -- (void)otherMouseDown:(NSEvent*)aEvent { [[self targetView] otherMouseDown:aEvent]; } -- (void)otherMouseUp:(NSEvent*)aEvent { [[self targetView] otherMouseUp:aEvent]; } -- (void)otherMouseDragged:(NSEvent*)aEvent { [[self targetView] otherMouseDragged:aEvent]; } -- (void)scrollWheel:(NSEvent*)aEvent { [[self targetView] scrollWheel:aEvent]; } -- (void)swipeWithEvent:(NSEvent*)aEvent { [[self targetView] swipeWithEvent:aEvent]; } -- (void)beginGestureWithEvent:(NSEvent*)aEvent { [[self targetView] beginGestureWithEvent:aEvent]; } -- (void)magnifyWithEvent:(NSEvent*)aEvent { [[self targetView] magnifyWithEvent:aEvent]; } -- (void)rotateWithEvent:(NSEvent*)aEvent { [[self targetView] rotateWithEvent:aEvent]; } -- (void)endGestureWithEvent:(NSEvent*)aEvent { [[self targetView] endGestureWithEvent:aEvent]; } -- (NSDragOperation)draggingEntered:(id )sender - { return [[self targetView] draggingEntered:sender]; } -- (NSDragOperation)draggingUpdated:(id )sender - { return [[self targetView] draggingUpdated:sender]; } -- (void)draggingExited:(id )sender - { [[self targetView] draggingExited:sender]; } -- (BOOL)performDragOperation:(id )sender - { return [[self targetView] performDragOperation:sender]; } -- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation - { [[self targetView] draggedImage:anImage endedAt:aPoint operation:operation]; } -- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal - { return [[self targetView] draggingSourceOperationMaskForLocal:isLocal]; } -- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination - { return [[self targetView] namesOfPromisedFilesDroppedAtDestination:dropDestination]; } -- (NSMenu*)menuForEvent:(NSEvent*)aEvent - { return [[self targetView] menuForEvent:aEvent]; } - -@end - -// This class allows us to exercise control over the window's title bar. This -// allows for a "unified toolbar" look, and for extending the content area into -// the title bar. It works like this: +// This class allows us to have a "unified toolbar" style window. It works like this: // 1) We set the window's style to textured. // 2) Because of this, the background color applies to the entire window, including // the titlebar area. For normal textured windows, the default pattern is a @@ -2708,11 +2617,6 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; // to the containing window - the other direction doesn't work. That's why the // toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply // query the window for its titlebar height when drawing the toolbar. -@interface ToolbarWindow(Private) -- (void)installTitlebarMouseHandlingView; -- (void)uninstallTitlebarMouseHandlingView; -@end; - @implementation ToolbarWindow - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag @@ -2747,7 +2651,6 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; [mColor release]; [mBackgroundColor release]; - [mTitlebarView release]; [super dealloc]; NS_OBJC_END_TRY_ABORT_BLOCK; @@ -2827,48 +2730,11 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw]; } -// Extending the content area into the title bar works by redirection of both -// drawing and mouse events. -// The window's NSView hierarchy looks like this: -// - border view ([[window contentView] superview]) -// - transparent title bar event redirection view -// - window controls (traffic light buttons) -// - content view ([window contentView], default NSView provided by the window) -// - our main Gecko ChildView ([window mainChildView]), which has an -// OpenGL context attached to it when accelerated -// - possibly more ChildViews for plugins -// -// When the window is in title bar extension mode, the mainChildView covers the -// whole window but is only visible in the content area of the window, because -// it's a subview of the window's contentView and thus clipped to its dimensions. -// This clipping is a good thing because it avoids a few problems. For example, -// if the mainChildView weren't clipped and thus visible in the titlebar, we'd -// have have to do the rounded corner masking and the drawing of the highlight -// line ourselves. -// This would be especially hard in combination with OpenGL acceleration since -// rounded corners would require making the OpenGL context transparent, which -// would bring another set of challenges with it. Having the window controls -// draw on top of an OpenGL context could be hard, too. -// -// So title bar drawing happens in the border view. The border view's drawRect -// method is not under our control, but we can get it to call into our code -// using some tricks, see the TitlebarAndBackgroundColor class below. -// Specifically, we have it call the TitlebarDrawCallback function, which -// draws the contents of mainChildView into the provided CGContext. -// (Even if the ChildView uses OpenGL for rendering, drawing in the title bar -// will happen non-accelerated in that CGContext.) -// -// Mouse event redirection happens via a TitlebarMouseHandlingView which we -// install below. - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState { BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState); [super setDrawsContentsIntoWindowFrame:aState]; if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) { - // Here we extend / shrink our mainChildView. We do that by firing a resize - // event which will cause the ChildView to be resized to the rect returned - // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return - // value on what we return from drawsContentsIntoWindowFrame. WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate]; nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget]; if (geckoWindow) { @@ -2883,35 +2749,10 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; // we'll send a mouse move event with the correct new position. ChildViewMouseTracker::ResendLastMouseMoveEvent(); - if (aState) { - [self installTitlebarMouseHandlingView]; - } else { - [self uninstallTitlebarMouseHandlingView]; - } + [self setTitlebarNeedsDisplayInRect:[self titlebarRect]]; } } -- (void)installTitlebarMouseHandlingView -{ - mTitlebarView = [[TitlebarMouseHandlingView alloc] initWithWindow:self]; - [[[self contentView] superview] addSubview:mTitlebarView positioned:NSWindowBelow relativeTo:nil]; -} - -- (void)uninstallTitlebarMouseHandlingView -{ - [mTitlebarView removeFromSuperview]; - [mTitlebarView release]; - mTitlebarView = nil; -} - -- (ChildView*)mainChildView -{ - NSView* view = [[[self contentView] subviews] lastObject]; - if (view && [view isKindOfClass:[ChildView class]]) - return (ChildView*)view; - return nil; -} - // Returning YES here makes the setShowsToolbarButton method work even though // the window doesn't contain an NSToolbar. - (BOOL)_hasToolbar @@ -2980,9 +2821,6 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) { nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget]; if (widget) { - if (type == NSMouseMoved) { - [[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent]; - } if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window)) return; if (widget->HasModalDescendents()) @@ -3048,8 +2886,8 @@ TitlebarDrawCallback(void* aInfo, CGContextRef aContext) NSRect titlebarRect = [window titlebarRect]; if ([window drawsContentsIntoWindowFrame]) { - ChildView* view = [window mainChildView]; - if (!view) + NSView* view = [[[window contentView] subviews] lastObject]; + if (!view || ![view isKindOfClass:[ChildView class]]) return; // Gecko drawing assumes flippedness, but the current context isn't flipped @@ -3060,7 +2898,7 @@ TitlebarDrawCallback(void* aInfo, CGContextRef aContext) CGContextTranslateCTM(aContext, 0.0f, -[window frame].size.height); NSRect flippedTitlebarRect = { NSZeroPoint, titlebarRect.size }; - [view drawRect:flippedTitlebarRect inTitlebarContext:aContext]; + [(ChildView*)view drawRect:flippedTitlebarRect inTitlebarContext:aContext]; } else { BOOL isMain = [window isMainWindow]; NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain]; From fd0415480e1de0c5f6747813045c9e5e168260bf Mon Sep 17 00:00:00 2001 From: Brad Lassey Date: Thu, 1 Nov 2012 09:31:58 -0400 Subject: [PATCH 04/43] bug 798826 - crash in gfxFT2FontList::FindFonts @ mozilla::scache::StartupCache::WaitOnWriteThread, speculative null check r=jfkthame --- gfx/thebes/gfxFT2FontList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gfx/thebes/gfxFT2FontList.cpp b/gfx/thebes/gfxFT2FontList.cpp index 86fdb89d724..a057054ee19 100644 --- a/gfx/thebes/gfxFT2FontList.cpp +++ b/gfx/thebes/gfxFT2FontList.cpp @@ -921,7 +921,7 @@ void ExtractFontsFromJar(nsIFile* aLocalDir) jarFile->GetLastModifiedTime(&jarModifiedTime); mozilla::scache::StartupCache* cache = mozilla::scache::StartupCache::GetSingleton(); - if (NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, &cachedModifiedTimeBuf, &longSize)) + if (cache && NS_SUCCEEDED(cache->GetBuffer(JAR_LAST_MODIFED_TIME, &cachedModifiedTimeBuf, &longSize)) && longSize == sizeof(int64_t)) { if (jarModifiedTime < *((int64_t*) cachedModifiedTimeBuf)) { return; @@ -979,7 +979,7 @@ void ExtractFontsFromJar(nsIFile* aLocalDir) } } } - if (allFontsExtracted) { + if (allFontsExtracted && cache) { cache->PutBuffer(JAR_LAST_MODIFED_TIME, (char*)&jarModifiedTime, sizeof(int64_t)); } } From da0f0b1bfcaf28e334b390aa2a0e879a512e21e0 Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Tue, 30 Oct 2012 20:12:19 -0400 Subject: [PATCH 05/43] Bug 807171 - Implement AudioParam.cancelScheduledValues; r=bzbarsky --- content/media/webaudio/AudioEventTimeline.h | 14 ++++++++++++- .../compiledtest/TestAudioEventTimeline.cpp | 20 +++++++++++++++++++ dom/webidl/AudioParam.webidl | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/content/media/webaudio/AudioEventTimeline.h b/content/media/webaudio/AudioEventTimeline.h index 54000d9c3e2..81867abb412 100644 --- a/content/media/webaudio/AudioEventTimeline.h +++ b/content/media/webaudio/AudioEventTimeline.h @@ -153,7 +153,19 @@ public: void CancelScheduledValues(float aStartTime) { - // TODO: implement + for (unsigned i = 0; i < mEvents.Length(); ++i) { + if (mEvents[i].mTime >= aStartTime) { +#ifdef DEBUG + // Sanity check: the array should be sorted, so all of the following + // events should have a time greater than aStartTime too. + for (unsigned j = i + 1; j < mEvents.Length(); ++j) { + MOZ_ASSERT(mEvents[j].mTime >= aStartTime); + } +#endif + mEvents.TruncateLength(i); + break; + } + } } // This method computes the AudioParam value at a given time based on the event timeline diff --git a/content/media/webaudio/compiledtest/TestAudioEventTimeline.cpp b/content/media/webaudio/compiledtest/TestAudioEventTimeline.cpp index 810306e0036..697a636d76e 100644 --- a/content/media/webaudio/compiledtest/TestAudioEventTimeline.cpp +++ b/content/media/webaudio/compiledtest/TestAudioEventTimeline.cpp @@ -208,6 +208,25 @@ void TestEventReplacement() is(timeline.GetValueAtTime(0.1f), 30.0f, "The first event should be overwritten"); } +void TestEventRemoval() +{ + Timeline timeline(10.0f, .1f, 20.0f); + + ErrorResultMock rv; + + timeline.SetValueAtTime(10.0f, 0.1f, rv); + timeline.SetValueAtTime(15.0f, 0.15f, rv); + timeline.SetValueAtTime(20.0f, 0.2f, rv); + timeline.LinearRampToValueAtTime(30.0f, 0.3f, rv); + is(timeline.GetEventCount(), 4, "Should have three events initially"); + timeline.CancelScheduledValues(0.4f); + is(timeline.GetEventCount(), 4, "Trying to delete past the end of the array should have no effect"); + timeline.CancelScheduledValues(0.3f); + is(timeline.GetEventCount(), 3, "Should successfully delete one event"); + timeline.CancelScheduledValues(0.12f); + is(timeline.GetEventCount(), 1, "Should successfully delete two events"); +} + void TestBeforeFirstEvent() { Timeline timeline(10.0f, .1f, 20.0f); @@ -327,6 +346,7 @@ int main() TestSpecExample(); TestInvalidEvents(); TestEventReplacement(); + TestEventRemoval(); TestBeforeFirstEvent(); TestAfterLastValueEvent(); TestAfterLastTargetValueEvent(); diff --git a/dom/webidl/AudioParam.webidl b/dom/webidl/AudioParam.webidl index 49d50cc09da..fc99adb8987 100644 --- a/dom/webidl/AudioParam.webidl +++ b/dom/webidl/AudioParam.webidl @@ -37,7 +37,7 @@ interface AudioParam { // void setValueCurveAtTime(Float32Array values, float startTime, float duration); // Cancels all scheduled parameter changes with times greater than or equal to startTime. - // void cancelScheduledValues(float startTime); + void cancelScheduledValues(float startTime); }; From 7afb9c40d077a1632612eebff94298d65da4da0a Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Wed, 31 Oct 2012 15:09:32 -0400 Subject: [PATCH 06/43] Bug 807526 - Implement GainNode; r=bzbarsky --- content/media/webaudio/AudioContext.cpp | 8 +++ content/media/webaudio/AudioContext.h | 4 ++ content/media/webaudio/GainNode.cpp | 42 ++++++++++++++ content/media/webaudio/GainNode.h | 51 ++++++++++++++++ content/media/webaudio/Makefile.in | 2 + content/media/webaudio/test/Makefile.in | 1 + .../media/webaudio/test/test_gainNode.html | 58 +++++++++++++++++++ dom/bindings/Bindings.conf | 5 ++ dom/webidl/AudioContext.webidl | 3 + dom/webidl/GainNode.webidl | 19 ++++++ dom/webidl/WebIDL.mk | 1 + 11 files changed, 194 insertions(+) create mode 100644 content/media/webaudio/GainNode.cpp create mode 100644 content/media/webaudio/GainNode.h create mode 100644 content/media/webaudio/test/test_gainNode.html create mode 100644 dom/webidl/GainNode.webidl diff --git a/content/media/webaudio/AudioContext.cpp b/content/media/webaudio/AudioContext.cpp index e2f2a1731f7..c5e6f4ca80f 100644 --- a/content/media/webaudio/AudioContext.cpp +++ b/content/media/webaudio/AudioContext.cpp @@ -12,6 +12,7 @@ #include "AudioDestinationNode.h" #include "AudioBufferSourceNode.h" #include "AudioBuffer.h" +#include "GainNode.h" namespace mozilla { namespace dom { @@ -86,6 +87,13 @@ AudioContext::CreateBuffer(JSContext* aJSContext, uint32_t aNumberOfChannels, return buffer.forget(); } +already_AddRefed +AudioContext::CreateGain() +{ + nsRefPtr gainNode = new GainNode(this); + return gainNode.forget(); +} + } } diff --git a/content/media/webaudio/AudioContext.h b/content/media/webaudio/AudioContext.h index 978b59b7819..6deaccafe67 100644 --- a/content/media/webaudio/AudioContext.h +++ b/content/media/webaudio/AudioContext.h @@ -26,6 +26,7 @@ namespace dom { class AudioDestinationNode; class AudioBufferSourceNode; class AudioBuffer; +class GainNode; class AudioContext MOZ_FINAL : public nsWrapperCache, public EnableWebAudioCheck @@ -61,6 +62,9 @@ public: uint32_t aLength, float aSampleRate, ErrorResult& aRv); + already_AddRefed + CreateGain(); + private: nsCOMPtr mWindow; nsRefPtr mDestination; diff --git a/content/media/webaudio/GainNode.cpp b/content/media/webaudio/GainNode.cpp new file mode 100644 index 00000000000..f4f1c23513c --- /dev/null +++ b/content/media/webaudio/GainNode.cpp @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GainNode.h" +#include "mozilla/dom/GainNodeBinding.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_CLASS(GainNode) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GainNode, AudioNode) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mGain) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(GainNode, AudioNode) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_PTR(tmp->mGain, AudioParam, "gain value") +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GainNode) +NS_INTERFACE_MAP_END_INHERITING(AudioNode) + +NS_IMPL_ADDREF_INHERITED(GainNode, AudioNode) +NS_IMPL_RELEASE_INHERITED(GainNode, AudioNode) + +GainNode::GainNode(AudioContext* aContext) + : AudioNode(aContext) + , mGain(new AudioParam(aContext, 1.0f, 0.0f, 1.0f)) +{ +} + +JSObject* +GainNode::WrapObject(JSContext* aCx, JSObject* aScope, + bool* aTriedToWrap) +{ + return GainNodeBinding::Wrap(aCx, aScope, this, aTriedToWrap); +} + +} +} + diff --git a/content/media/webaudio/GainNode.h b/content/media/webaudio/GainNode.h new file mode 100644 index 00000000000..1d391f03234 --- /dev/null +++ b/content/media/webaudio/GainNode.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef GainNode_h_ +#define GainNode_h_ + +#include "AudioNode.h" +#include "AudioParam.h" + +namespace mozilla { +namespace dom { + +class AudioContext; + +class GainNode : public AudioNode +{ +public: + explicit GainNode(AudioContext* aContext); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GainNode, AudioNode) + + virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope, + bool* aTriedToWrap); + + virtual uint32_t MaxNumberOfInputs() const MOZ_FINAL MOZ_OVERRIDE + { + return 1; + } + virtual uint32_t MaxNumberOfOutputs() const MOZ_FINAL MOZ_OVERRIDE + { + return 1; + } + + AudioParam* Gain() const + { + return mGain; + } + +private: + nsRefPtr mGain; +}; + +} +} + +#endif + diff --git a/content/media/webaudio/Makefile.in b/content/media/webaudio/Makefile.in index cedf11972d7..777d9fe1737 100644 --- a/content/media/webaudio/Makefile.in +++ b/content/media/webaudio/Makefile.in @@ -23,6 +23,7 @@ CPPSRCS := \ AudioParam.cpp \ AudioSourceNode.cpp \ EnableWebAudioCheck.cpp \ + GainNode.cpp \ $(NULL) EXPORTS_NAMESPACES := mozilla/dom @@ -33,6 +34,7 @@ EXPORTS_mozilla/dom := \ AudioNode.h \ AudioParam.h \ AudioSourceNode.h \ + GainNode.h \ $(NULL) PARALLEL_DIRS := test diff --git a/content/media/webaudio/test/Makefile.in b/content/media/webaudio/test/Makefile.in index 011e13a0811..7111a27bdea 100644 --- a/content/media/webaudio/test/Makefile.in +++ b/content/media/webaudio/test/Makefile.in @@ -14,6 +14,7 @@ MOCHITEST_FILES := \ test_AudioBuffer.html \ test_AudioContext.html \ test_badConnect.html \ + test_gainNode.html \ test_singleSourceDest.html \ $(NULL) diff --git a/content/media/webaudio/test/test_gainNode.html b/content/media/webaudio/test/test_gainNode.html new file mode 100644 index 00000000000..98e7320210e --- /dev/null +++ b/content/media/webaudio/test/test_gainNode.html @@ -0,0 +1,58 @@ + + + + Test GainNode + + + + +
+
+
+ + diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 3a674738fdf..34f118cf32d 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -202,6 +202,11 @@ DOMInterfaces = { 'workers': True, }], +'GainNode': [ +{ + 'resultNotAddRefed': [ 'gain' ], +}], + 'HTMLCollection': [ { 'nativeType': 'nsIHTMLCollection', diff --git a/dom/webidl/AudioContext.webidl b/dom/webidl/AudioContext.webidl index 4948c832cd1..f92733690f9 100644 --- a/dom/webidl/AudioContext.webidl +++ b/dom/webidl/AudioContext.webidl @@ -25,6 +25,9 @@ interface mozAudioContext { [Creator] AudioBufferSourceNode createBufferSource(); + [Creator] + GainNode createGain(); + }; typedef mozAudioContext AudioContext; diff --git a/dom/webidl/GainNode.webidl b/dom/webidl/GainNode.webidl new file mode 100644 index 00000000000..8c561a592b8 --- /dev/null +++ b/dom/webidl/GainNode.webidl @@ -0,0 +1,19 @@ +/* -*- 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/. + * + * The origin of this IDL file is + * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html + * + * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C + * liability, trademark and document use rules apply. + */ + +[PrefControlled] +interface GainNode : AudioNode { + + readonly attribute AudioParam gain; + +}; + diff --git a/dom/webidl/WebIDL.mk b/dom/webidl/WebIDL.mk index 1420a8a2031..225c8aaa74c 100644 --- a/dom/webidl/WebIDL.mk +++ b/dom/webidl/WebIDL.mk @@ -29,6 +29,7 @@ webidl_files = \ EventTarget.webidl \ FileList.webidl \ FileReaderSync.webidl \ + GainNode.webidl \ HTMLCollection.webidl \ HTMLOptionsCollection.webidl \ HTMLPropertiesCollection.webidl \ From 4eea2e6cebaf86abfaddd1b2305e01af53defdcc Mon Sep 17 00:00:00 2001 From: Axel Hecht Date: Thu, 1 Nov 2012 13:25:53 +0100 Subject: [PATCH 07/43] bug 797745, use l10n-base and relativesrcdir instead of config.mk for l10n-merge, r=ted --HG-- extra : rebase_source : 0d280e5cfabe7efdbe112309da6ef87d3848c441 --- config/JarMaker.py | 42 +++++++++++++++++++++++++++++++++-- config/config.mk | 20 ++++++++++------- config/tests/unit-JarMaker.py | 37 ++++++++++++++++++++++++++++++ js/src/config/config.mk | 20 ++++++++++------- 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/config/JarMaker.py b/config/JarMaker.py index 4b1f6d7d3b4..24a6380ffe1 100644 --- a/config/JarMaker.py +++ b/config/JarMaker.py @@ -77,6 +77,9 @@ class JarMaker(object): self.topsourcedir = None self.sourcedirs = [] self.localedirs = None + self.l10nbase = None + self.l10nmerge = None + self.relativesrcdir = None def getCommandLineParser(self): '''Get a optparse.OptionParser for jarmaker. @@ -105,6 +108,12 @@ class JarMaker(object): help="top source directory") p.add_option('-c', '--l10n-src', type="string", action="append", help="localization directory") + p.add_option('--l10n-base', type="string", action="store", + help="base directory to be used for localization (requires relativesrcdir)") + p.add_option('--locale-mergedir', type="string", action="store", + help="base directory to be used for l10n-merge (requires l10n-base and relativesrcdir)") + p.add_option('--relativesrcdir', type="string", + help="relativesrcdir to be used for localization") p.add_option('-j', type="string", help="jarfile directory") return p @@ -170,7 +179,7 @@ class JarMaker(object): mf.close() finally: lock = None - + def makeJar(self, infile, jardir): '''makeJar is the main entry point to JarMaker. @@ -183,6 +192,8 @@ class JarMaker(object): self.sourcedirs = [_normpath(p) for p in self.sourcedirs] if self.localedirs: self.localedirs = [_normpath(p) for p in self.localedirs] + elif self.relativesrcdir: + self.localedirs = self.generateLocaleDirs(self.relativesrcdir) if isinstance(infile, basestring): logging.info("processing " + infile) self.sourcedirs.append(_normpath(os.path.dirname(infile))) @@ -205,6 +216,23 @@ class JarMaker(object): pass return + def generateLocaleDirs(self, relativesrcdir): + if os.path.basename(relativesrcdir) == 'locales': + # strip locales + l10nrelsrcdir = os.path.dirname(relativesrcdir) + else: + l10nrelsrcdir = relativesrcdir + locdirs = [] + # generate locales dirs, merge, l10nbase, en-US + if self.l10nmerge: + locdirs.append(os.path.join(self.l10nmerge, l10nrelsrcdir)) + if self.l10nbase: + locdirs.append(os.path.join(self.l10nbase, l10nrelsrcdir)) + if self.l10nmerge or not self.l10nbase: + # add en-US if we merge, or if it's not l10n + locdirs.append(os.path.join(self.topsourcedir, relativesrcdir, 'en-US')) + return locdirs + def processJarSection(self, jarfile, lines, jardir): '''Internal method called by makeJar to actually process a section of a jar.mn file. @@ -323,7 +351,7 @@ class JarMaker(object): outf.write(inf.read()) outf.close() inf.close() - + class OutputHelper_jar(object): '''Provide getDestModTime and getOutput for a given jarfile. @@ -402,6 +430,16 @@ def main(): if options.bothManifests: jm.useChromeManifest = True jm.useJarfileManifest = True + if options.l10n_base: + if not options.relativesrcdir: + p.error('relativesrcdir required when using l10n-base') + if options.l10n_src: + p.error('both l10n-src and l10n-base are not supported') + jm.l10nbase = options.l10n_base + jm.relativesrcdir = options.relativesrcdir + jm.l10nmerge = options.locale_mergedir + elif options.locale_mergedir: + p.error('l10n-base required when using locale-mergedir') jm.localedirs = options.l10n_src noise = logging.INFO if options.verbose is not None: diff --git a/config/config.mk b/config/config.mk index d13dc5c4188..b02d9888a93 100644 --- a/config/config.mk +++ b/config/config.mk @@ -703,17 +703,21 @@ ifdef relativesrcdir LOCALE_SRCDIR = $(call EXPAND_LOCALE_SRCDIR,$(relativesrcdir)) endif -ifdef LOCALE_SRCDIR -# if LOCALE_MERGEDIR is set, use mergedir first, then the localization, -# and finally en-US +ifdef relativesrcdir +MAKE_JARS_FLAGS += --relativesrcdir=$(relativesrcdir) +ifneq (en-US,$(AB_CD)) ifdef LOCALE_MERGEDIR -MAKE_JARS_FLAGS += -c $(LOCALE_MERGEDIR)/$(subst /locales,,$(relativesrcdir)) +MAKE_JARS_FLAGS += --locale-mergedir=$(LOCALE_MERGEDIR) endif +ifdef IS_LANGUAGE_REPACK +MAKE_JARS_FLAGS += --l10n-base=$(L10NBASEDIR) +endif +else MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR) -ifdef LOCALE_MERGEDIR -MAKE_JARS_FLAGS += -c $(topsrcdir)/$(relativesrcdir)/en-US -endif -endif +endif # en-US +else +MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR) +endif # ! relativesrcdir ifdef LOCALE_MERGEDIR MERGE_FILE = $(firstword \ diff --git a/config/tests/unit-JarMaker.py b/config/tests/unit-JarMaker.py index aa4d3c963c0..6ae5af4814f 100644 --- a/config/tests/unit-JarMaker.py +++ b/config/tests/unit-JarMaker.py @@ -4,6 +4,7 @@ import os, sys, os.path, time, inspect from filecmp import dircmp from tempfile import mkdtemp from shutil import rmtree, copy2 +from StringIO import StringIO from zipfile import ZipFile import mozunit from JarMaker import JarMaker @@ -240,5 +241,41 @@ class TestJarMaker(unittest.TestCase): "%s is not a symlink to %s" % (destfoo, srcbar)) +class Test_relativesrcdir(unittest.TestCase): + def setUp(self): + self.jm = JarMaker() + self.jm.topsourcedir = '/TOPSOURCEDIR' + self.jm.relativesrcdir = 'browser/locales' + self.fake_empty_file = StringIO() + self.fake_empty_file.name = 'fake_empty_file' + def tearDown(self): + del self.jm + del self.fake_empty_file + def test_en_US(self): + jm = self.jm + jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED') + self.assertEquals(jm.localedirs, + [ + os.path.join(os.path.abspath('/TOPSOURCEDIR'), + 'browser/locales', 'en-US') + ]) + def test_l10n_no_merge(self): + jm = self.jm + jm.l10nbase = '/L10N_BASE' + jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED') + self.assertEquals(jm.localedirs, [os.path.join('/L10N_BASE', 'browser')]) + def test_l10n_merge(self): + jm = self.jm + jm.l10nbase = '/L10N_BASE' + jm.l10nmerge = '/L10N_MERGE' + jm.makeJar(self.fake_empty_file, '/NO_OUTPUT_REQUIRED') + self.assertEquals(jm.localedirs, + [os.path.join('/L10N_MERGE', 'browser'), + os.path.join('/L10N_BASE', 'browser'), + os.path.join(os.path.abspath('/TOPSOURCEDIR'), + 'browser/locales', 'en-US') + ]) + + if __name__ == '__main__': mozunit.main() diff --git a/js/src/config/config.mk b/js/src/config/config.mk index d13dc5c4188..b02d9888a93 100644 --- a/js/src/config/config.mk +++ b/js/src/config/config.mk @@ -703,17 +703,21 @@ ifdef relativesrcdir LOCALE_SRCDIR = $(call EXPAND_LOCALE_SRCDIR,$(relativesrcdir)) endif -ifdef LOCALE_SRCDIR -# if LOCALE_MERGEDIR is set, use mergedir first, then the localization, -# and finally en-US +ifdef relativesrcdir +MAKE_JARS_FLAGS += --relativesrcdir=$(relativesrcdir) +ifneq (en-US,$(AB_CD)) ifdef LOCALE_MERGEDIR -MAKE_JARS_FLAGS += -c $(LOCALE_MERGEDIR)/$(subst /locales,,$(relativesrcdir)) +MAKE_JARS_FLAGS += --locale-mergedir=$(LOCALE_MERGEDIR) endif +ifdef IS_LANGUAGE_REPACK +MAKE_JARS_FLAGS += --l10n-base=$(L10NBASEDIR) +endif +else MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR) -ifdef LOCALE_MERGEDIR -MAKE_JARS_FLAGS += -c $(topsrcdir)/$(relativesrcdir)/en-US -endif -endif +endif # en-US +else +MAKE_JARS_FLAGS += -c $(LOCALE_SRCDIR) +endif # ! relativesrcdir ifdef LOCALE_MERGEDIR MERGE_FILE = $(firstword \ From e0920683501944150f5a63ae6d300a411226760e Mon Sep 17 00:00:00 2001 From: Axel Hecht Date: Thu, 1 Nov 2012 15:51:48 +0100 Subject: [PATCH 08/43] bug 797745, add support for relativesrcdir overrides, r=ted relativesrcdir is used for l10n packaging primarily. Being able to override this in jar.mn allows to package individual files from toolkit for an app. --HG-- extra : rebase_source : 1b5b9028751cfe01a3810cea81a52b4c20919b51 --- config/JarMaker.py | 6 ++++++ config/tests/unit-JarMaker.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/config/JarMaker.py b/config/JarMaker.py index 24a6380ffe1..51dbd884084 100644 --- a/config/JarMaker.py +++ b/config/JarMaker.py @@ -64,6 +64,7 @@ class JarMaker(object): ignore = re.compile('\s*(\#.*)?$') jarline = re.compile('(?:(?P[\w\d.\-\_\\\/]+).jar\:)|(?:\s*(\#.*)?)\s*$') + relsrcline = re.compile('relativesrcdir\s+(?P.+?):') regline = re.compile('\%\s+(.*)$') entryre = '(?P\*)?(?P\+?)\s+' entryline = re.compile(entryre + '(?P[\w\d.\-\_\\\/\+\@]+)\s*(\((?P\%?)(?P[\w\d.\-\_\\\/\@]+)\))?\s*$') @@ -282,6 +283,11 @@ class JarMaker(object): raise if self.ignore.match(l): continue + m = self.relsrcline.match(l) + if m: + relativesrcdir = m.group('relativesrcdir') + self.localedirs = self.generateLocaleDirs(relativesrcdir) + continue m = self.regline.match(l) if m: rline = m.group(1) diff --git a/config/tests/unit-JarMaker.py b/config/tests/unit-JarMaker.py index 6ae5af4814f..3bb8b080e65 100644 --- a/config/tests/unit-JarMaker.py +++ b/config/tests/unit-JarMaker.py @@ -275,6 +275,29 @@ class Test_relativesrcdir(unittest.TestCase): os.path.join(os.path.abspath('/TOPSOURCEDIR'), 'browser/locales', 'en-US') ]) + def test_override(self): + jm = self.jm + jm.outputFormat = 'flat' # doesn't touch chrome dir without files + jarcontents = StringIO('''en-US.jar: +relativesrcdir dom/locales: +''') + jarcontents.name = 'override.mn' + jm.makeJar(jarcontents, '/NO_OUTPUT_REQUIRED') + self.assertEquals(jm.localedirs, + [ + os.path.join(os.path.abspath('/TOPSOURCEDIR'), + 'dom/locales', 'en-US') + ]) + def test_override_l10n(self): + jm = self.jm + jm.l10nbase = '/L10N_BASE' + jm.outputFormat = 'flat' # doesn't touch chrome dir without files + jarcontents = StringIO('''en-US.jar: +relativesrcdir dom/locales: +''') + jarcontents.name = 'override.mn' + jm.makeJar(jarcontents, '/NO_OUTPUT_REQUIRED') + self.assertEquals(jm.localedirs, [os.path.join('/L10N_BASE', 'dom')]) if __name__ == '__main__': From 049ffc6f35118387689ce6cf32c20d96936e1870 Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Thu, 1 Nov 2012 08:08:21 -0700 Subject: [PATCH 09/43] Bug 807535 - Avoid toggling Ion write barrier too often (r=sstangl) --- js/src/jscompartment.cpp | 7 +++++-- js/src/jscompartment.h | 8 +++++++- js/src/jsgc.cpp | 21 +++++++++++++-------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index e539f22e10e..b7e173c4b97 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -48,6 +48,7 @@ JSCompartment::JSCompartment(JSRuntime *rt) gcStoreBuffer(&gcNursery), #endif needsBarrier_(false), + ionUsingBarriers_(false), gcScheduled(false), gcState(NoGC), gcPreserveCode(false), @@ -131,7 +132,7 @@ JSCompartment::init(JSContext *cx) } void -JSCompartment::setNeedsBarrier(bool needs) +JSCompartment::setNeedsBarrier(bool needs, ShouldUpdateIon updateIon) { #ifdef JS_METHODJIT /* ClearAllFrames calls compileBarriers() and needs the old value. */ @@ -141,8 +142,10 @@ JSCompartment::setNeedsBarrier(bool needs) #endif #ifdef JS_ION - if (needsBarrier_ != needs) + if (updateIon == UpdateIon && needs != ionUsingBarriers_) { ion::ToggleBarriers(this, needs); + ionUsingBarriers_ = needs; + } #endif needsBarrier_ = needs; diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index c15e59ba596..99de89bd684 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -155,6 +155,7 @@ struct JSCompartment private: bool needsBarrier_; + bool ionUsingBarriers_; public: bool needsBarrier() const { @@ -169,7 +170,12 @@ struct JSCompartment return compileBarriers(needsBarrier()); } - void setNeedsBarrier(bool needs); + enum ShouldUpdateIon { + DontUpdateIon, + UpdateIon + }; + + void setNeedsBarrier(bool needs, ShouldUpdateIon updateIon); static size_t OffsetOfNeedsBarrier() { return offsetof(JSCompartment, needsBarrier_); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index f21bb69109e..5a9c37b5194 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -4079,7 +4079,7 @@ ResetIncrementalGC(JSRuntime *rt, const char *reason) AutoCopyFreeListToArenas copy(rt); for (GCCompartmentsIter c(rt); !c.done(); c.next()) { if (c->isGCMarking()) { - c->setNeedsBarrier(false); + c->setNeedsBarrier(false, JSCompartment::DontUpdateIon); c->setGCState(JSCompartment::NoGC); wasMarking = true; } @@ -4137,10 +4137,15 @@ AutoGCSlice::AutoGCSlice(JSRuntime *rt) rt->stackSpace.markActiveCompartments(); for (GCCompartmentsIter c(rt); !c.done(); c.next()) { - /* Clear this early so we don't do any write barriers during GC. */ + /* + * Clear needsBarrier early so we don't do any write barriers during + * GC. We don't need to update the Ion barriers (which is expensive) + * because Ion code doesn't run during GC. If need be, we'll update the + * Ion barriers in ~AutoGCSlice. + */ if (c->isGCMarking()) { JS_ASSERT(c->needsBarrier()); - c->setNeedsBarrier(false); + c->setNeedsBarrier(false, JSCompartment::DontUpdateIon); } else { JS_ASSERT(!c->needsBarrier()); } @@ -4151,11 +4156,11 @@ AutoGCSlice::~AutoGCSlice() { for (GCCompartmentsIter c(runtime); !c.done(); c.next()) { if (c->isGCMarking()) { - c->setNeedsBarrier(true); + c->setNeedsBarrier(true, JSCompartment::UpdateIon); c->arenas.prepareForIncrementalGC(runtime); } else { JS_ASSERT(c->isGCSweeping()); - c->setNeedsBarrier(false); + c->setNeedsBarrier(false, JSCompartment::UpdateIon); } } } @@ -4312,7 +4317,7 @@ IncrementalCollectSlice(JSRuntime *rt, default: JS_ASSERT(false); - } + } } class IncrementalSafety @@ -5267,7 +5272,7 @@ StartVerifyPreBarriers(JSRuntime *rt) rt->gcMarker.start(rt); for (CompartmentsIter c(rt); !c.done(); c.next()) { PurgeJITCaches(c); - c->setNeedsBarrier(true); + c->setNeedsBarrier(true, JSCompartment::UpdateIon); c->arenas.purge(); } @@ -5350,7 +5355,7 @@ EndVerifyPreBarriers(JSRuntime *rt) compartmentCreated = true; PurgeJITCaches(c); - c->setNeedsBarrier(false); + c->setNeedsBarrier(false, JSCompartment::UpdateIon); } /* From dbb4c044f989819ab8ffd40f9d38e534eae749ef Mon Sep 17 00:00:00 2001 From: Alexander Surkov Date: Fri, 2 Nov 2012 01:05:33 +0900 Subject: [PATCH 10/43] Bug 807596 - crash [@ Accessible::GetActionRule()], r=hub --- accessible/src/generic/Accessible.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accessible/src/generic/Accessible.cpp b/accessible/src/generic/Accessible.cpp index 64bd57f375e..a936ed6fc84 100644 --- a/accessible/src/generic/Accessible.cpp +++ b/accessible/src/generic/Accessible.cpp @@ -3084,7 +3084,7 @@ Accessible::GetAttrValue(nsIAtom *aProperty, double *aValue) uint32_t Accessible::GetActionRule() { - if (InteractiveState() & states::UNAVAILABLE) + if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) return eNoAction; // Check if it's simple xlink. From 6004d8da7de90d33633c9346b51c37aca852a8ce Mon Sep 17 00:00:00 2001 From: pushkarsingh Date: Thu, 1 Nov 2012 09:43:47 -0700 Subject: [PATCH 11/43] Bug 700678 - Exit full screen mode when the app goes into the background.r=margaret --- mobile/android/chrome/content/browser.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 3e70e3f5596..c8ca1f558df 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -6586,6 +6586,10 @@ var ActivityObserver = { let isForeground = false switch (aTopic) { case "application-background" : + let doc = BrowserApp.selectedTab.browser.contentDocument; + if (doc.mozFullScreen) { + doc.mozCancelFullScreen(); + } isForeground = false; break; case "application-foreground" : From de551246c595372dc3de231baf52d2617ef535e8 Mon Sep 17 00:00:00 2001 From: Mark Capella Date: Thu, 1 Nov 2012 12:55:19 -0400 Subject: [PATCH 12/43] Bug 806454 - Remove sessionCache code from SessionStore component, r=bnicholson --- mobile/android/components/SessionStore.js | 60 ----------------------- 1 file changed, 60 deletions(-) diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js index ff7677233e6..23a88de8f96 100644 --- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -52,20 +52,11 @@ SessionStore.prototype = { // Get file references this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile); this._sessionFileBackup = this._sessionFile.clone(); - this._sessionCache = this._sessionFile.clone(); this._sessionFile.append("sessionstore.js"); this._sessionFileBackup.append("sessionstore.bak"); - this._sessionCache.append("sessionstoreCache"); this._loadState = STATE_STOPPED; - try { - if (!this._sessionCache.exists() || !this._sessionCache.isDirectory()) - this._sessionCache.create(Ci.nsIFile.DIRECTORY_TYPE, 0700); - } catch (ex) { - Cu.reportError(ex); // file was write-locked? - } - this._interval = Services.prefs.getIntPref("browser.sessionstore.interval"); this._maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo"); @@ -88,36 +79,6 @@ SessionStore.prototype = { } catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now? } - this._clearCache(); - }, - - _clearCache: function ss_clearCache() { - // First, let's get a list of files we think should be active - let activeFiles = []; - this._forEachBrowserWindow(function(aWindow) { - let tabs = aWindow.BrowserApp.tabs; - for (let i = 0; i < tabs.length; i++) { - let browser = tabs[i].browser; - if (browser.__SS_extdata && "thumbnail" in browser.__SS_extdata) - activeFiles.push(browser.__SS_extdata.thumbnail); - } - }); - - // Now, let's find the stale files in the cache folder - let staleFiles = []; - let cacheFiles = this._sessionCache.directoryEntries; - while (cacheFiles.hasMoreElements()) { - let file = cacheFiles.getNext().QueryInterface(Ci.nsILocalFile); - let fileURI = Services.io.newFileURI(file); - if (activeFiles.indexOf(fileURI) == -1) - staleFiles.push(file); - } - - // Remove the stale files in a separate step to keep the enumerator from - // messing up if we remove the files as we collect them. - staleFiles.forEach(function(aFile) { - aFile.remove(false); - }) }, _sendMessageToJava: function (aMsg) { @@ -928,26 +889,6 @@ SessionStore.prototype = { setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) { let browser = aTab.browser; - // Thumbnails are actually stored in the cache, so do the save and update the URI - if (aKey == "thumbnail") { - let file = this._sessionCache.clone(); - file.append("thumbnail-" + browser.contentWindowId); - file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600); - - let source = Services.io.newURI(aStringValue, "UTF8", null); - let target = Services.io.newFileURI(file) - - let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(Ci.nsIWebBrowserPersist); - persist.persistFlags = Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; - let privacyContext = browser.contentWindow - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsILoadContext); - persist.saveURI(source, null, null, null, null, file, privacyContext); - - aStringValue = target.spec; - } - if (!browser.__SS_extdata) browser.__SS_extdata = {}; browser.__SS_extdata[aKey] = aStringValue; @@ -978,7 +919,6 @@ SessionStore.prototype = { } function notifyObservers(aMessage) { - self._clearCache(); Services.obs.notifyObservers(null, "sessionstore-windows-restored", aMessage || ""); } From c1f2c52fbf3e86d3078b6d1cf6e609ff13952df6 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:31 +0000 Subject: [PATCH 13/43] Bug 785945 - Add a version of the combined view without the images table join (r=mfinkle) --- .../android/base/db/BrowserProvider.java.in | 91 ++++++++++++++++++- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/mobile/android/base/db/BrowserProvider.java.in b/mobile/android/base/db/BrowserProvider.java.in index aace301d0b5..674f4377a3d 100644 --- a/mobile/android/base/db/BrowserProvider.java.in +++ b/mobile/android/base/db/BrowserProvider.java.in @@ -69,7 +69,7 @@ public class BrowserProvider extends ContentProvider { static final String DATABASE_NAME = "browser.db"; - static final int DATABASE_VERSION = 11; + static final int DATABASE_VERSION = 12; // Maximum age of deleted records to be cleaned up (20 days in ms) static final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20; @@ -100,6 +100,8 @@ public class BrowserProvider extends ContentProvider { static final String VIEW_BOOKMARKS_WITH_IMAGES = "bookmarks_with_images"; static final String VIEW_HISTORY_WITH_IMAGES = "history_with_images"; + + static final String VIEW_COMBINED = "combined"; static final String VIEW_COMBINED_WITH_IMAGES = "combined_with_images"; // Bookmark matches @@ -644,6 +646,74 @@ public class BrowserProvider extends ContentProvider { " ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL)); } + private void createCombinedViewOn12(SQLiteDatabase db) { + debug("Creating " + VIEW_COMBINED + " view"); + + db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" + + " SELECT " + Combined.BOOKMARK_ID + ", " + + Combined.HISTORY_ID + ", " + + // We need to return an _id column because CursorAdapter requires it for its + // default implementation for the getItemId() method. However, since + // we're not using this feature in the parts of the UI using this view, + // we can just use 0 for all rows. + "0 AS " + Combined._ID + ", " + + Combined.URL + ", " + + Combined.TITLE + ", " + + Combined.VISITS + ", " + + Combined.DISPLAY + ", " + + Combined.DATE_LAST_VISITED + + " FROM (" + + // Bookmarks without history. + " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " + + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + + Bookmarks.FIXED_READING_LIST_ID + " THEN " + Combined.DISPLAY_READER + " ELSE " + + Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " + + "-1 AS " + Combined.HISTORY_ID + ", " + + "-1 AS " + Combined.VISITS + ", " + + "-1 AS " + Combined.DATE_LAST_VISITED + + " FROM " + TABLE_BOOKMARKS + + " WHERE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + + " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" + + " UNION ALL" + + // History with and without bookmark. + " SELECT " + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " ELSE NULL END AS " + Combined.BOOKMARK_ID + ", " + + qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + ", " + + // Prioritze bookmark titles over history titles, since the user may have + // customized the title for a bookmark. + "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " + + qualifyColumn(TABLE_HISTORY, History.TITLE) +")" + " AS " + Combined.TITLE + ", " + + // Only use DISPLAY_READER if the matching bookmark entry inside reading + // list folder is not marked as deleted. + "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " WHEN 0 THEN CASE " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " WHEN " + Bookmarks.FIXED_READING_LIST_ID + + " THEN " + Combined.DISPLAY_READER + " ELSE " + Combined.DISPLAY_NORMAL + " END ELSE " + + Combined.DISPLAY_NORMAL + " END AS " + Combined.DISPLAY + ", " + + qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + ", " + + qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + ", " + + qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + + " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS + + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) + + " WHERE " + qualifyColumn(TABLE_HISTORY, History.URL) + " IS NOT NULL AND " + + qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND (" + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " + + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ") " + + ")"); + + debug("Creating " + VIEW_COMBINED_WITH_IMAGES + " view"); + + db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_IMAGES + " AS" + + " SELECT *, " + + qualifyColumn(TABLE_IMAGES, Images.FAVICON) + " AS " + Combined.FAVICON + ", " + + qualifyColumn(TABLE_IMAGES, Images.THUMBNAIL) + " AS " + Combined.THUMBNAIL + + " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_IMAGES + + " ON " + Combined.URL + " = " + qualifyColumn(TABLE_IMAGES, Images.URL)); + } + @Override public void onCreate(SQLiteDatabase db) { debug("Creating browser.db: " + db.getPath()); @@ -651,10 +721,10 @@ public class BrowserProvider extends ContentProvider { createBookmarksTable(db); createHistoryTable(db); createImagesTable(db); + createCombinedViewOn12(db); createBookmarksWithImagesView(db); createHistoryWithImagesView(db); - createCombinedWithImagesViewOn11(db); createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID, R.string.bookmarks_folder_places, 0); @@ -1091,6 +1161,13 @@ public class BrowserProvider extends ContentProvider { createCombinedWithImagesViewOn11(db); } + private void upgradeDatabaseFrom11to12(SQLiteDatabase db) { + debug("Dropping view: " + VIEW_COMBINED_WITH_IMAGES); + db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_IMAGES); + + createCombinedViewOn12(db); + } + @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { debug("Upgrading browser.db: " + db.getPath() + " from " + @@ -1141,6 +1218,10 @@ public class BrowserProvider extends ContentProvider { case 11: upgradeDatabaseFrom10to11(db); break; + + case 12: + upgradeDatabaseFrom11to12(db); + break; } } @@ -1931,7 +2012,11 @@ public class BrowserProvider extends ContentProvider { groupBy = Combined.URL; qb.setProjectionMap(COMBINED_PROJECTION_MAP); - qb.setTables(VIEW_COMBINED_WITH_IMAGES); + + if (hasImagesInProjection(projection)) + qb.setTables(VIEW_COMBINED_WITH_IMAGES); + else + qb.setTables(VIEW_COMBINED); break; } From 144d09fb3e7d4ea1caca54402982bfea837e1f3e Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:31 +0000 Subject: [PATCH 14/43] Bug 785945 - Add API to get multiple favicons at once from DB (r=mfinkle) --- mobile/android/base/db/BrowserDB.java | 8 ++++++++ mobile/android/base/db/LocalBrowserDB.java | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index 5d9b264ac78..f00bc2e5f0d 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -12,6 +12,8 @@ import android.database.ContentObserver; import android.database.Cursor; import android.graphics.drawable.BitmapDrawable; +import java.util.List; + public class BrowserDB { public static String ABOUT_PAGES_URL_FILTER = "about:%"; @@ -73,6 +75,8 @@ public class BrowserDB { public BitmapDrawable getFaviconForUrl(ContentResolver cr, String uri); + public Cursor getFaviconsForUrls(ContentResolver cr, List urls); + public void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon); public void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail); @@ -186,6 +190,10 @@ public class BrowserDB { return sDb.getFaviconForUrl(cr, uri); } + public static Cursor getFaviconsForUrls(ContentResolver cr, List urls) { + return sDb.getFaviconsForUrls(cr, urls); + } + public static void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon) { sDb.updateFaviconForUrl(cr, uri, favicon); } diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 2adeb74ad38..f2b8fc80873 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -31,6 +31,7 @@ import android.util.Log; import java.io.ByteArrayOutputStream; import java.util.Collection; import java.util.HashMap; +import java.util.List; public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // Calculate these once, at initialization. isLoggable is too expensive to @@ -619,6 +620,27 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { return new BitmapDrawable(bitmap); } + public Cursor getFaviconsForUrls(ContentResolver cr, List urls) { + StringBuffer selection = new StringBuffer(); + String[] selectionArgs = new String[urls.size()]; + + for (int i = 0; i < urls.size(); i++) { + final String url = urls.get(i); + + if (i > 0) + selection.append(" OR "); + + selection.append(Images.URL + " = ?"); + selectionArgs[i] = url; + } + + return cr.query(mImagesUriWithProfile, + new String[] { Images.URL, Images.FAVICON }, + selection.toString(), + selectionArgs, + null); + } + public void updateFaviconForUrl(ContentResolver cr, String uri, BitmapDrawable favicon) { Bitmap bitmap = favicon.getBitmap(); From f5625270bd9efdbd0cfda8495b0b99f7a9c6ea25 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:31 +0000 Subject: [PATCH 15/43] Bug 785945 - Add LRU cache implementation (r=mfinkle) --- mobile/android/base/Makefile.in | 1 + mobile/android/base/util/LruCache.java | 323 +++++++++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 mobile/android/base/util/LruCache.java diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 99df785e9c6..1b567d81dfd 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -32,6 +32,7 @@ UTIL_JAVA_FILES := \ INISection.java \ util/EventDispatcher.java \ util/FloatUtils.java \ + util/LruCache.java \ $(NULL) FENNEC_JAVA_FILES = \ diff --git a/mobile/android/base/util/LruCache.java b/mobile/android/base/util/LruCache.java new file mode 100644 index 00000000000..ee6265e004a --- /dev/null +++ b/mobile/android/base/util/LruCache.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mozilla.gecko.util; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Static library version of {@link android.util.LruCache}. Used to write apps + * that run on API levels prior to 12. When running on API level 12 or above, + * this implementation is still used; it does not try to switch to the + * framework's implementation. See the framework SDK documentation for a class + * overview. + */ +public class LruCache { + private final LinkedHashMap map; + + /** Size of this cache in units. Not necessarily the number of elements. */ + private int size; + private int maxSize; + + private int putCount; + private int createCount; + private int evictionCount; + private int hitCount; + private int missCount; + + /** + * @param maxSize for caches that do not override {@link #sizeOf}, this is + * the maximum number of entries in the cache. For all other caches, + * this is the maximum sum of the sizes of the entries in this cache. + */ + public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap(0, 0.75f, true); + } + + /** + * Returns the value for {@code key} if it exists in the cache or can be + * created by {@code #create}. If a value was returned, it is moved to the + * head of the queue. This returns null if a value is not cached and cannot + * be created. + */ + public final V get(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + hitCount++; + return mapValue; + } + missCount++; + } + + /* + * Attempt to create a value. This may take a long time, and the map + * may be different when create() returns. If a conflicting value was + * added to the map while create() was working, we leave that value in + * the map and release the created value. + */ + + V createdValue = create(key); + if (createdValue == null) { + return null; + } + + synchronized (this) { + createCount++; + mapValue = map.put(key, createdValue); + + if (mapValue != null) { + // There was a conflict so undo that last put + map.put(key, mapValue); + } else { + size += safeSizeOf(key, createdValue); + } + } + + if (mapValue != null) { + entryRemoved(false, key, createdValue, mapValue); + return mapValue; + } else { + trimToSize(maxSize); + return createdValue; + } + } + + /** + * Caches {@code value} for {@code key}. The value is moved to the head of + * the queue. + * + * @return the previous value mapped by {@code key}. + */ + public final V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + V previous; + synchronized (this) { + putCount++; + size += safeSizeOf(key, value); + previous = map.put(key, value); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize); + return previous; + } + + /** + * @param maxSize the maximum size of the cache before returning. May be -1 + * to evict even 0-sized elements. + */ + private void trimToSize(int maxSize) { + while (true) { + K key; + V value; + synchronized (this) { + if (size < 0 || (map.isEmpty() && size != 0)) { + throw new IllegalStateException(getClass().getName() + + ".sizeOf() is reporting inconsistent results!"); + } + + if (size <= maxSize || map.isEmpty()) { + break; + } + + Map.Entry toEvict = map.entrySet().iterator().next(); + key = toEvict.getKey(); + value = toEvict.getValue(); + map.remove(key); + size -= safeSizeOf(key, value); + evictionCount++; + } + + entryRemoved(true, key, value, null); + } + } + + /** + * Removes the entry for {@code key} if it exists. + * + * @return the previous value mapped by {@code key}. + */ + public final V remove(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V previous; + synchronized (this) { + previous = map.remove(key); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, null); + } + + return previous; + } + + /** + * Called for entries that have been evicted or removed. This method is + * invoked when a value is evicted to make space, removed by a call to + * {@link #remove}, or replaced by a call to {@link #put}. The default + * implementation does nothing. + * + *

The method is called without synchronization: other threads may + * access the cache while this method is executing. + * + * @param evicted true if the entry is being removed to make space, false + * if the removal was caused by a {@link #put} or {@link #remove}. + * @param newValue the new value for {@code key}, if it exists. If non-null, + * this removal was caused by a {@link #put}. Otherwise it was caused by + * an eviction or a {@link #remove}. + */ + protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} + + /** + * Called after a cache miss to compute a value for the corresponding key. + * Returns the computed value or null if no value can be computed. The + * default implementation returns null. + * + *

The method is called without synchronization: other threads may + * access the cache while this method is executing. + * + *

If a value for {@code key} exists in the cache when this method + * returns, the created value will be released with {@link #entryRemoved} + * and discarded. This can occur when multiple threads request the same key + * at the same time (causing multiple values to be created), or when one + * thread calls {@link #put} while another is creating a value for the same + * key. + */ + protected V create(K key) { + return null; + } + + private int safeSizeOf(K key, V value) { + int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + * Returns the size of the entry for {@code key} and {@code value} in + * user-defined units. The default implementation returns 1 so that size + * is the number of entries and max size is the maximum number of entries. + * + *

An entry's size must not change while it is in the cache. + */ + protected int sizeOf(K key, V value) { + return 1; + } + + /** + * Clear the cache, calling {@link #entryRemoved} on each removed entry. + */ + public final void evictAll() { + trimToSize(-1); // -1 will evict 0-sized elements + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the number + * of entries in the cache. For all other caches, this returns the sum of + * the sizes of the entries in this cache. + */ + public synchronized final int size() { + return size; + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the maximum + * number of entries in the cache. For all other caches, this returns the + * maximum sum of the sizes of the entries in this cache. + */ + public synchronized final int maxSize() { + return maxSize; + } + + /** + * Returns the number of times {@link #get} returned a value. + */ + public synchronized final int hitCount() { + return hitCount; + } + + /** + * Returns the number of times {@link #get} returned null or required a new + * value to be created. + */ + public synchronized final int missCount() { + return missCount; + } + + /** + * Returns the number of times {@link #create(Object)} returned a value. + */ + public synchronized final int createCount() { + return createCount; + } + + /** + * Returns the number of times {@link #put} was called. + */ + public synchronized final int putCount() { + return putCount; + } + + /** + * Returns the number of values that have been evicted. + */ + public synchronized final int evictionCount() { + return evictionCount; + } + + /** + * Returns a copy of the current contents of the cache, ordered from least + * recently accessed to most recently accessed. + */ + public synchronized final Map snapshot() { + return new LinkedHashMap(map); + } + + @Override public synchronized final String toString() { + int accesses = hitCount + missCount; + int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; + return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", + maxSize, hitCount, missCount, hitPercent); + } +} From c0b03818fcf7df300bf59f61d90cffe7e7fca90e Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:32 +0000 Subject: [PATCH 16/43] Bug 785945 - Add a LRU memory cache to Favicons and corresponding API (r=mfinkle) --- mobile/android/base/Favicons.java | 59 +++++++++++++++++++++----- mobile/android/base/MemoryMonitor.java | 2 + 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/mobile/android/base/Favicons.java b/mobile/android/base/Favicons.java index 1c0f4243494..ab0d53860bf 100644 --- a/mobile/android/base/Favicons.java +++ b/mobile/android/base/Favicons.java @@ -7,6 +7,7 @@ package org.mozilla.gecko; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.util.GeckoJarReader; +import org.mozilla.gecko.util.LruCache; import org.apache.http.HttpEntity; import org.apache.http.client.methods.HttpGet; @@ -19,6 +20,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; +import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.http.AndroidHttpClient; @@ -46,6 +48,7 @@ public class Favicons { private Map mLoadTasks; private long mNextFaviconLoadId; + private LruCache mFaviconsCache; private static final String USER_AGENT = GeckoApp.mAppContext.getDefaultUAString(); private AndroidHttpClient mHttpClient; @@ -139,6 +142,15 @@ public class Favicons { mLoadTasks = Collections.synchronizedMap(new HashMap()); mNextFaviconLoadId = 0; + + // Create a favicon memory cache that have up to 1mb of size + mFaviconsCache = new LruCache(1024 * 1024) { + @Override + protected int sizeOf(String url, Drawable image) { + Bitmap bitmap = ((BitmapDrawable) image).getBitmap(); + return bitmap.getRowBytes() * bitmap.getHeight(); + } + }; } private synchronized AndroidHttpClient getHttpClient() { @@ -149,6 +161,20 @@ public class Favicons { return mHttpClient; } + private void dispatchResult(final String pageUrl, final Drawable image, + final OnFaviconLoadedListener listener) { + if (pageUrl != null && image != null) + putFaviconInMemCache(pageUrl, image); + + // We want to always run the listener on UI thread + GeckoApp.mAppContext.runOnUiThread(new Runnable() { + public void run() { + if (listener != null) + listener.onFaviconLoaded(pageUrl, image); + } + }); + } + public String getFaviconUrlForPageUrl(String pageUrl) { return mDbHelper.getFaviconUrlForPageUrl(pageUrl); } @@ -158,8 +184,15 @@ public class Favicons { // Handle the case where page url is empty if (pageUrl == null || pageUrl.length() == 0) { - if (listener != null) - listener.onFaviconLoaded(null, null); + dispatchResult(null, null, listener); + return -1; + } + + // Check if favicon is mem cached + Drawable image = getFaviconFromMemCache(pageUrl); + if (image != null) { + dispatchResult(pageUrl, image, listener); + return -1; } LoadFaviconTask task = new LoadFaviconTask(pageUrl, faviconUrl, persist, listener); @@ -172,6 +205,18 @@ public class Favicons { return taskId; } + public Drawable getFaviconFromMemCache(String pageUrl) { + return mFaviconsCache.get(pageUrl); + } + + public void putFaviconInMemCache(String pageUrl, Drawable image) { + mFaviconsCache.put(pageUrl, image); + } + + public void clearMemCache() { + mFaviconsCache.evictAll(); + } + public boolean cancelFaviconLoad(long taskId) { Log.d(LOGTAG, "Requesting cancelation of favicon load (" + taskId + ")"); @@ -339,15 +384,7 @@ public class Favicons { @Override protected void onPostExecute(final BitmapDrawable image) { mLoadTasks.remove(mId); - - if (mListener != null) { - // We want to always run the listener on UI thread - GeckoApp.mAppContext.runOnUiThread(new Runnable() { - public void run() { - mListener.onFaviconLoaded(mPageUrl, image); - } - }); - } + dispatchResult(mPageUrl, image, mListener); } @Override diff --git a/mobile/android/base/MemoryMonitor.java b/mobile/android/base/MemoryMonitor.java index fef6eb1e785..206f4ad0b2f 100644 --- a/mobile/android/base/MemoryMonitor.java +++ b/mobile/android/base/MemoryMonitor.java @@ -156,6 +156,8 @@ class MemoryMonitor extends BroadcastReceiver { } ScreenshotHandler.disableScreenshot(false); GeckoAppShell.geckoEventSync(); + + GeckoApp.mAppContext.getFavicons().clearMemCache(); } } From 0a22ff83f94d8472f10212cf7fde1b0c515fd0c9 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:32 +0000 Subject: [PATCH 17/43] Bug 785945 - Load favicon images asynchronously in the All Pages tab (r=mfinkle) --- .../android/base/awesomebar/AllPagesTab.java | 155 +++++++++++++++++- mobile/android/base/db/LocalBrowserDB.java | 1 - 2 files changed, 152 insertions(+), 4 deletions(-) diff --git a/mobile/android/base/awesomebar/AllPagesTab.java b/mobile/android/base/awesomebar/AllPagesTab.java index caee2ad3a6f..26eca1f0723 100644 --- a/mobile/android/base/awesomebar/AllPagesTab.java +++ b/mobile/android/base/awesomebar/AllPagesTab.java @@ -7,8 +7,10 @@ package org.mozilla.gecko; import org.mozilla.gecko.AwesomeBar.ContextMenuSubject; import org.mozilla.gecko.db.BrowserContract.Combined; +import org.mozilla.gecko.db.BrowserContract.Images; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.util.GeckoAsyncTask; import org.mozilla.gecko.util.GeckoEventListener; import org.json.JSONArray; @@ -18,8 +20,13 @@ import org.json.JSONObject; import android.app.Activity; import android.content.Context; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.os.Handler; +import android.os.Message; import android.os.SystemClock; import android.text.TextUtils; import android.util.Log; @@ -49,6 +56,7 @@ import android.widget.TextView; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.List; public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { public static final String LOGTAG = "ALL_PAGES"; @@ -68,6 +76,11 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { private LinearLayout mAllPagesView; private boolean mAnimateSuggestions; private View mSuggestionsOptInPrompt; + private Handler mHandler; + + private static final int MESSAGE_LOAD_FAVICONS = 1; + private static final int MESSAGE_UPDATE_FAVICONS = 2; + private static final int DELAY_SHOW_THUMBNAILS = 550; private class SearchEntryViewHolder { public FlowLayout suggestionView; @@ -122,9 +135,14 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { ((Activity)mContext).registerForContextMenu(mView); mView.setTag(TAG); AwesomeBarCursorAdapter adapter = getCursorAdapter(); - ((ListView)mView).setAdapter(adapter); - mView.setOnTouchListener(mListListener); + + ListView listView = (ListView) mView; + listView.setAdapter(adapter); + listView.setOnTouchListener(mListListener); + + mHandler = new AllPagesHandler(); } + return (ListView)mView; } @@ -138,6 +156,10 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { Cursor cursor = adapter.getCursor(); if (cursor != null) cursor.close(); + + mHandler.removeMessages(MESSAGE_UPDATE_FAVICONS); + mHandler.removeMessages(MESSAGE_LOAD_FAVICONS); + mHandler = null; } public void filter(String searchTerm) { @@ -196,6 +218,8 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { Cursor c = BrowserDB.filter(getContentResolver(), constraint, MAX_RESULTS); c.getCount(); + postLoadFavicons(); + long end = SystemClock.uptimeMillis(); int time = (int)(end - start); Log.i(LOGTAG, "Got cursor in " + time + "ms"); @@ -398,8 +422,8 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { updateTitle(viewHolder.titleView, cursor); updateUrl(viewHolder.urlView, cursor); - updateFavicon(viewHolder.faviconView, cursor); updateBookmarkIcon(viewHolder.bookmarkIconView, cursor); + displayFavicon(viewHolder); } return convertView; @@ -706,4 +730,129 @@ public class AllPagesTab extends AwesomeBarTab implements GeckoEventListener { private void unregisterEventListener(String event) { GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this); } + + private List getUrlsWithoutFavicon() { + List urls = new ArrayList(); + + Cursor c = mCursorAdapter.getCursor(); + if (c == null || !c.moveToFirst()) + return urls; + + do { + final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); + + // We only want to load favicons from DB if they are not in the + // memory cache yet. + Favicons favicons = GeckoApp.mAppContext.getFavicons(); + if (favicons.getFaviconFromMemCache(url) != null) + continue; + + urls.add(url); + } while (c.moveToNext()); + + return urls; + } + + public void storeFaviconsInMemCache(Cursor c) { + try { + if (c == null || !c.moveToFirst()) + return; + + Favicons favicons = GeckoApp.mAppContext.getFavicons(); + + do { + final String url = c.getString(c.getColumnIndexOrThrow(Images.URL)); + final byte[] b = c.getBlob(c.getColumnIndexOrThrow(Images.FAVICON)); + if (b == null) + continue; + + Bitmap favicon = BitmapFactory.decodeByteArray(b, 0, b.length); + if (favicon == null) + continue; + + Drawable faviconDrawable = new BitmapDrawable(getResources(), favicon); + favicons.putFaviconInMemCache(url, faviconDrawable); + } while (c.moveToNext()); + } finally { + if (c != null) + c.close(); + } + } + + private void loadFaviconsForCurrentResults() { + final List urls = getUrlsWithoutFavicon(); + if (urls.size() == 0) + return; + + (new GeckoAsyncTask(GeckoApp.mAppContext, GeckoAppShell.getHandler()) { + @Override + public Cursor doInBackground(Void... params) { + return BrowserDB.getFaviconsForUrls(getContentResolver(), urls); + } + + @Override + public void onPostExecute(Cursor c) { + storeFaviconsInMemCache(c); + postUpdateFavicons(); + } + }).execute(); + } + + private void displayFavicon(AwesomeEntryViewHolder viewHolder) { + final String url = viewHolder.urlView.getText().toString(); + Favicons favicons = GeckoApp.mAppContext.getFavicons(); + viewHolder.faviconView.setImageDrawable(favicons.getFaviconFromMemCache(url)); + } + + private void updateFavicons() { + ListView listView = (ListView) mView; + for (int i = 0; i < listView.getChildCount(); i++) { + final View view = listView.getChildAt(i); + final Object tag = view.getTag(); + + if (tag == null || !(tag instanceof AwesomeEntryViewHolder)) + continue; + + final AwesomeEntryViewHolder viewHolder = (AwesomeEntryViewHolder) tag; + displayFavicon(viewHolder); + } + + mView.invalidate(); + } + + private void postUpdateFavicons() { + if (mHandler == null) + return; + + Message msg = mHandler.obtainMessage(MESSAGE_UPDATE_FAVICONS, + AllPagesTab.this); + + mHandler.removeMessages(MESSAGE_UPDATE_FAVICONS); + mHandler.sendMessage(msg); + } + + private void postLoadFavicons() { + if (mHandler == null) + return; + + Message msg = mHandler.obtainMessage(MESSAGE_LOAD_FAVICONS, + AllPagesTab.this); + + mHandler.removeMessages(MESSAGE_LOAD_FAVICONS); + mHandler.sendMessageDelayed(msg, 200); + } + + private class AllPagesHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_LOAD_FAVICONS: + loadFaviconsForCurrentResults(); + break; + case MESSAGE_UPDATE_FAVICONS: + updateFavicons(); + break; + } + } + } } diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index f2b8fc80873..fd954305db1 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -182,7 +182,6 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { new String[] { Combined._ID, Combined.URL, Combined.TITLE, - Combined.FAVICON, Combined.DISPLAY, Combined.BOOKMARK_ID, Combined.HISTORY_ID }, From d38247ae26400afebbb3a355aa4cfac9b55dec29 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:32 +0000 Subject: [PATCH 18/43] Bug 785945 - Add API to get multiple thumbnails at once from DB (r=mfinkle) --- mobile/android/base/db/BrowserDB.java | 6 ++++++ mobile/android/base/db/LocalBrowserDB.java | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index f00bc2e5f0d..422443e57ff 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -83,6 +83,8 @@ public class BrowserDB { public byte[] getThumbnailForUrl(ContentResolver cr, String uri); + public Cursor getThumbnailsForUrls(ContentResolver cr, List urls); + public void removeThumbnails(ContentResolver cr); public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer); @@ -206,6 +208,10 @@ public class BrowserDB { return sDb.getThumbnailForUrl(cr, uri); } + public static Cursor getThumbnailsForUrls(ContentResolver cr, List urls) { + return sDb.getThumbnailsForUrls(cr, urls); + } + public static void removeThumbnails(ContentResolver cr) { sDb.removeThumbnails(cr); } diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index fd954305db1..75377ba8335 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -708,6 +708,27 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { return b; } + public Cursor getThumbnailsForUrls(ContentResolver cr, List urls) { + StringBuffer selection = new StringBuffer(); + String[] selectionArgs = new String[urls.size()]; + + for (int i = 0; i < urls.size(); i++) { + final String url = urls.get(i); + + if (i > 0) + selection.append(" OR "); + + selection.append(Images.URL + " = ?"); + selectionArgs[i] = url; + } + + return cr.query(mImagesUriWithProfile, + new String[] { Images.URL, Images.THUMBNAIL }, + selection.toString(), + selectionArgs, + null); + } + public void removeThumbnails(ContentResolver cr) { ContentValues values = new ContentValues(); values.putNull(Images.THUMBNAIL); From ca63a6480ee2fd3fdfa8543c02393794692e7e79 Mon Sep 17 00:00:00 2001 From: Lucas Rocha Date: Wed, 31 Oct 2012 12:34:32 +0000 Subject: [PATCH 19/43] Bug 785945 - Load top site thumbnails asynchronously in about:home (r=mfinkle) --- mobile/android/base/AboutHomeContent.java | 130 ++++++++++++++---- mobile/android/base/db/LocalBrowserDB.java | 3 +- .../android/base/resources/values/styles.xml | 2 +- 3 files changed, 102 insertions(+), 33 deletions(-) diff --git a/mobile/android/base/AboutHomeContent.java b/mobile/android/base/AboutHomeContent.java index 7b58ef6aa01..189b26c1bf5 100644 --- a/mobile/android/base/AboutHomeContent.java +++ b/mobile/android/base/AboutHomeContent.java @@ -7,8 +7,10 @@ package org.mozilla.gecko; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.db.BrowserDB.URLColumns; +import org.mozilla.gecko.db.BrowserContract.Images; import org.mozilla.gecko.sync.setup.SyncAccounts; import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity; +import org.mozilla.gecko.util.GeckoAsyncTask; import org.json.JSONArray; import org.json.JSONException; @@ -59,8 +61,10 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -263,9 +267,8 @@ public class AboutHomeContent extends ScrollView mTopSitesAdapter = new TopSitesCursorAdapter(mActivity, R.layout.abouthome_topsite_item, mCursor, - new String[] { URLColumns.TITLE, - URLColumns.THUMBNAIL }, - new int[] { R.id.title, R.id.thumbnail }); + new String[] { URLColumns.TITLE }, + new int[] { R.id.title }); mTopSitesAdapter.setViewBinder(new TopSitesViewBinder()); mTopSitesGrid.setAdapter(mTopSitesAdapter); @@ -273,6 +276,9 @@ public class AboutHomeContent extends ScrollView mTopSitesAdapter.changeCursor(mCursor); } + if (mTopSitesAdapter.getCount() > 0) + loadTopSitesThumbnails(resolver); + updateLayout(syncIsSetup); // Free the old Cursor in the right thread now. @@ -288,6 +294,97 @@ public class AboutHomeContent extends ScrollView }); } + private List getTopSitesUrls() { + List urls = new ArrayList(); + + Cursor c = mTopSitesAdapter.getCursor(); + if (c == null || !c.moveToFirst()) + return urls; + + do { + final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL)); + urls.add(url); + } while (c.moveToNext()); + + return urls; + } + + private void displayThumbnail(View view, Bitmap thumbnail) { + ImageView thumbnailView = (ImageView) view.findViewById(R.id.thumbnail); + + if (thumbnail == null) { + thumbnailView.setImageResource(R.drawable.abouthome_thumbnail_bg); + thumbnailView.setScaleType(ImageView.ScaleType.FIT_CENTER); + } else { + try { + thumbnailView.setImageBitmap(thumbnail); + thumbnailView.setScaleType(ImageView.ScaleType.CENTER_CROP); + } catch (OutOfMemoryError oom) { + Log.e(LOGTAG, "Unable to load thumbnail bitmap", oom); + thumbnailView.setImageResource(R.drawable.abouthome_thumbnail_bg); + thumbnailView.setScaleType(ImageView.ScaleType.FIT_CENTER); + } + } + } + + private void updateTopSitesThumbnails(Map thumbnails) { + for (int i = 0; i < mTopSitesGrid.getChildCount(); i++) { + final View view = mTopSitesGrid.getChildAt(i); + + Cursor c = (Cursor) mTopSitesGrid.getItemAtPosition(i); + final String url = c.getString(c.getColumnIndex(URLColumns.URL)); + + displayThumbnail(view, thumbnails.get(url)); + } + + mTopSitesGrid.invalidate(); + } + + public Map getTopSitesThumbnails(Cursor c) { + Map thumbnails = new HashMap(); + + try { + if (c == null || !c.moveToFirst()) + return thumbnails; + + do { + final String url = c.getString(c.getColumnIndexOrThrow(Images.URL)); + final byte[] b = c.getBlob(c.getColumnIndexOrThrow(Images.THUMBNAIL)); + if (b == null) + continue; + + Bitmap thumbnail = BitmapFactory.decodeByteArray(b, 0, b.length); + if (thumbnail == null) + continue; + + thumbnails.put(url, thumbnail); + } while (c.moveToNext()); + } finally { + if (c != null) + c.close(); + } + + return thumbnails; + } + + private void loadTopSitesThumbnails(final ContentResolver cr) { + final List urls = getTopSitesUrls(); + if (urls.size() == 0) + return; + + (new GeckoAsyncTask(GeckoApp.mAppContext, GeckoAppShell.getHandler()) { + @Override + public Cursor doInBackground(Void... params) { + return BrowserDB.getThumbnailsForUrls(cr, urls); + } + + @Override + public void onPostExecute(Cursor c) { + updateTopSitesThumbnails(getTopSitesThumbnails(c)); + } + }).execute(); + } + void update(final EnumSet flags) { GeckoAppShell.getHandler().post(new Runnable() { public void run() { @@ -675,28 +772,6 @@ public class AboutHomeContent extends ScrollView } class TopSitesViewBinder implements SimpleCursorAdapter.ViewBinder { - private boolean updateThumbnail(View view, Cursor cursor, int thumbIndex) { - byte[] b = cursor.getBlob(thumbIndex); - ImageView thumbnail = (ImageView) view; - - if (b == null) { - thumbnail.setImageResource(R.drawable.abouthome_thumbnail_bg); - thumbnail.setScaleType(ImageView.ScaleType.FIT_CENTER); - } else { - try { - Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length); - thumbnail.setImageBitmap(bitmap); - thumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP); - } catch (OutOfMemoryError oom) { - Log.e(LOGTAG, "Unable to load thumbnail bitmap", oom); - thumbnail.setImageResource(R.drawable.abouthome_thumbnail_bg); - thumbnail.setScaleType(ImageView.ScaleType.FIT_CENTER); - } - } - - return true; - } - private boolean updateTitle(View view, Cursor cursor, int titleIndex) { String title = cursor.getString(titleIndex); TextView titleView = (TextView) view; @@ -719,11 +794,6 @@ public class AboutHomeContent extends ScrollView return updateTitle(view, cursor, titleIndex); } - int thumbIndex = cursor.getColumnIndexOrThrow(URLColumns.THUMBNAIL); - if (columnIndex == thumbIndex) { - return updateThumbnail(view, cursor, thumbIndex); - } - // Other columns are handled automatically return false; } diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 75377ba8335..8526a9b65b9 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -194,8 +194,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { return filterAllSites(cr, new String[] { Combined._ID, Combined.URL, - Combined.TITLE, - Combined.THUMBNAIL }, + Combined.TITLE }, "", limit, BrowserDB.ABOUT_PAGES_URL_FILTER); diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml index 8a9484741c8..30cea98f0f5 100644 --- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -218,7 +218,7 @@ @dimen/abouthome_icon_radius 0dip 0dip - centerCrop + fitCenter