diff --git a/mobile/chrome/content/BrowserView.js b/mobile/chrome/content/BrowserView.js index e81f42298761..a2cbf03f88b7 100644 --- a/mobile/chrome/content/BrowserView.js +++ b/mobile/chrome/content/BrowserView.js @@ -332,6 +332,12 @@ BrowserView.prototype = { this.browserToViewport(browserH), true); bvs.zoomChanged = true; + + if (this._browser) { + let event = document.createEvent("Events"); + event.initEvent("ZoomChanged", true, false); + this._browser.dispatchEvent(event); + } } }, @@ -401,6 +407,12 @@ BrowserView.prototype = { */ pauseRendering: function pauseRendering() { this._renderMode++; + if (this._renderMode == 1 && this._browser) { + let event = document.createEvent("Events"); + event.initEvent("RenderStateChanged", true, false); + event.isRendering = false; + this._browser.dispatchEvent(event); + } }, /** @@ -414,8 +426,16 @@ BrowserView.prototype = { if (renderNow || this._renderMode == 0) this.renderNow(); - if (this._renderMode == 0) + if (this._renderMode == 0) { this._idleServiceObserver.resumeCrawls(); + + if (this._browser) { + let event = document.createEvent("Events"); + event.initEvent("RenderStateChanged", true, false); + event.isRendering = true; + this._browser.dispatchEvent(event); + } + } }, /** diff --git a/mobile/chrome/content/Util.js b/mobile/chrome/content/Util.js index ce3e823d7769..5da7c4aebdd8 100644 --- a/mobile/chrome/content/Util.js +++ b/mobile/chrome/content/Util.js @@ -45,7 +45,6 @@ let Ci = Components.interfaces; // let Util = { - bind: function bind(f, thisObj) { return function() { return f.apply(thisObj, arguments); diff --git a/mobile/chrome/content/browser-ui.js b/mobile/chrome/content/browser-ui.js index c9dd6d39c664..dbdd31bee37f 100644 --- a/mobile/chrome/content/browser-ui.js +++ b/mobile/chrome/content/browser-ui.js @@ -72,6 +72,11 @@ const TOOLBARSTATE_LOADED = 2; "gFocusManager", "@mozilla.org/focus-manager;1", [Ci.nsIFocusManager] + ], + [ + "gObserverService", + "@mozilla.org/observer-service;1", + [Ci.nsIObserverService] ] ].forEach(function (service) { let [name, contract, ifaces] = service; @@ -201,6 +206,11 @@ var BrowserUI = { }, _toolbarLocked: 0, + + isToolbarLocked: function isToolbarLocked() { + return this._toolbarLocked; + }, + lockToolbar: function lockToolbar() { this._toolbarLocked++; document.getElementById("toolbar-moveable-container").top = "0"; diff --git a/mobile/chrome/content/browser.js b/mobile/chrome/content/browser.js index 18edba337af4..56ec25f36fc9 100644 --- a/mobile/chrome/content/browser.js +++ b/mobile/chrome/content/browser.js @@ -581,11 +581,15 @@ var Browser = { // Force commonly used border-images into the image cache ImagePreloader.cache(); + + this._pluginObserver = new PluginObserver(bv); + new PreferenceToggle("plugins.enabled", this._pluginObserver); }, shutdown: function() { this._browserView.uninit(); BrowserUI.uninit(); + this._pluginObserver.stop(); var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); os.removeObserver(gXPInstallObserver, "xpinstall-install-blocked"); @@ -730,7 +734,8 @@ var Browser = { this._selectedTab.scrollOffset = this.getScrollboxPosition(this.contentScrollboxScroller); } - let firstTab = this._selectedTab == null; + let isFirstTab = this._selectedTab == null; + let lastTab = this._selectedTab; this._selectedTab = tab; tab.ensureBrowserExists(); @@ -742,13 +747,14 @@ var Browser = { document.getElementById("tabs").selectedTab = tab.chromeTab; - if (!firstTab) { + if (!isFirstTab) { // Update all of our UI to reflect the new tab's location BrowserUI.updateURI(); getIdentityHandler().checkIdentity(); let event = document.createEvent("Events"); event.initEvent("TabSelect", true, false); + event.lastTab = lastTab; tab.chromeTab.dispatchEvent(event); } @@ -1105,8 +1111,10 @@ var Browser = { }, getBoundingContentRect: function getBoundingContentRect(contentElem) { - let browser = Browser._browserView.getBrowser(); - + let tab = Browser.getTabForDocument(contentElem.ownerDocument); + if (!tab) + return null; + let browser = tab.browser; if (!browser) return null; @@ -1151,6 +1159,12 @@ var Browser = { return (arguments.length > 1) ? [x - x0, y - y0] : (x - x0); }, + browserViewToClientRect: function browserViewToClientRect(rect) { + let container = document.getElementById("tile-container"); + let containerBCR = container.getBoundingClientRect(); + return rect.clone().translate(Math.round(containerBCR.left), Math.round(containerBCR.top)); + }, + /** * turn client coordinates into page-relative ones (adjusted for * zoom and page position) @@ -2761,3 +2775,181 @@ var ImagePreloader = { } } } + +/** Call obj.start() or stop() based on boolean pref. */ +function PreferenceToggle(pref, obj) { + // Make weak reference just to be sure there are no cycles + gPrefService.addObserver(pref, this, true); + this._obj = obj; + this._pref = pref; + this._check(); +} + +PreferenceToggle.prototype = { + QueryInterface: function QueryInterface(iid) { + if (iid.equals(Ci.nsIObserver) || + iid.equals(Ci.nsISupportsWeakReference) || + iid.equals(Ci.nsISupports)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + observe: function observe(subject, topic, data) { + if (topic == "nsPref:changed" && data == this._pref) + this._check(); + }, + + _check: function _check() { + try { + let value = gPrefService.getBoolPref(this._pref); + value ? this._obj.start() : this._obj.stop(); + } catch(e) { + this._obj.stop(); + } + } +}; + +const nsIObjectLoadingContent = Ci.nsIObjectLoadingContent_MOZILLA_1_9_2_BRANCH || Ci.nsIObjectLoadingContent; + +/** + * Allows fast-path embed rendering by letting objects know where to absolutely + * render on the screen. + */ +function PluginObserver(bv) { + this._emptyRect = new Rect(0, 0, 0, 0); + this._contentShowing = document.getElementById("observe_contentShowing"); + this._bv = bv; + this._started = false; +} + +PluginObserver.prototype = { + /** Starts flash objects fast path. */ + start: function() { + if (this._started) + return; + this._started = true; + + document.getElementById("tabs-container").addEventListener("TabSelect", this, false); + this._contentShowing.addEventListener("broadcast", this, false); + let browsers = document.getElementById("browsers"); + browsers.addEventListener("RenderStateChanged", this, false); + gObserverService.addObserver(this, "plugin-changed-event", false); + + let browser = Browser.selectedBrowser; + if (browser) { + browser.addEventListener("ZoomChanged", this, false); + browser.addEventListener("MozAfterPaint", this, false); + } + }, + + /** Stops listening for events. */ + stop: function() { + if (!this._started) + return; + this._started = false; + + document.getElementById("tabs-container").removeEventListener("TabSelect", this, false); + this._contentShowing.removeEventListener("broadcast", this, false); + let browsers = document.getElementById("browsers"); + browsers.removeEventListener("RenderStateChanged", this, false); + gObserverService.removeObserver(this, "plugin-changed-event"); + + let browser = Browser.selectedBrowser; + if (browser) { + browser.removeEventListener("ZoomChanged", this, false); + browser.removeEventListener("MozAfterPaint", this, false); + } + }, + + /** Observe listens for plugin change events and maintains an embed cache. */ + observe: function observe(subject, topic, data) { + let doc = subject.ownerDocument; + if (data == "init") { + if (doc.pluginCache === undefined) + doc.pluginCache = []; + doc.pluginCache.push(subject); + } else if (data == "reflow") { + this.updateCurrentBrowser(); + } else if (data == "destroy") { + if (doc.pluginCache) { + let index = doc.pluginCache.indexOf(subject); + if (index != -1) + doc.pluginCache.splice(index, 1); + } + } + }, + + /** Update flash objects */ + handleEvent: function handleEvent(ev) { + if (ev.type == "TabSelect") { + if (ev.lastTab) { + let browser = ev.lastTab.browser; + let oldDoc = browser.contentDocument; + browser.removeEventListener("ZoomChanged", this, false); + browser.removeEventListener("MozAfterPaint", this, false); + if (oldDoc.pluginCache) + this.updateEmbedRegions(oldDoc.pluginCache, this._emptyRect); + } + + let browser = Browser.selectedBrowser; + browser.addEventListener("ZoomChanged", this, false); + browser.addEventListener("MozAfterPaint", this, false); + } + + this.updateCurrentBrowser(); + }, + + /** Update the current browser's flash objects. */ + updateCurrentBrowser: function updateCurrentBrowser() { + let doc = Browser.selectedTab.browser.contentDocument; + if (!doc.pluginCache) + return; + + let rect = this.getCriticalRect(); + if (rect == this._emptyRect) { + // Always stop rendering immediately. + this.updateEmbedRegions(doc.pluginCache, rect); + } else { + // Wait a moment so that any chrome redraws occur first. + let self = this; + setTimeout(function() { + // Recalculate critical rect so we don't render when we ought not to. + self.updateEmbedRegions(doc.pluginCache, self.getCriticalRect()); + }, 0); + } + }, + + /** More accurate version of finding the current visible region. */ + // XXX should visible rect call take some of these things into account? + getCriticalRect: function getCriticalRect() { + let bv = this._bv; + if (!bv.isRendering()) + return this._emptyRect; + if (Elements.contentShowing.hasAttribute("disabled")) + return this._emptyRect; + + let vs = bv._browserViewportState; + let vr = bv.getVisibleRect(); + let crit = BrowserView.Util.visibleRectToCriticalRect(vr, vs); + + if (BrowserUI.isToolbarLocked()) { + let urlbar = document.getElementById("toolbar-moveable-container"); + let height = urlbar.getBoundingClientRect().height; + crit.top += height; + } + return crit; + }, + + /** Tell embedded objects where to absolutely render, using crit for clipping. */ + updateEmbedRegions: function updateEmbedRegions(objects, crit) { + let bv = this._bv; + let oprivate, r, dest, clip; + for (let i = objects.length - 1; i >= 0; i--) { + r = bv.browserToViewportRect(Browser.getBoundingContentRect(objects[i])); + dest = Browser.browserViewToClientRect(r); + clip = Browser.browserViewToClientRect(r.intersect(crit)).translate(-dest.left, -dest.top); + oprivate = objects[i].QueryInterface(nsIObjectLoadingContent); + oprivate.setAbsoluteScreenPosition(Browser.contentScrollbox, dest, clip); + } + }, +}; diff --git a/mobile/chrome/content/browser.xul b/mobile/chrome/content/browser.xul index 405ac6860103..81d733b7b7cb 100644 --- a/mobile/chrome/content/browser.xul +++ b/mobile/chrome/content/browser.xul @@ -93,6 +93,10 @@ + + + +