diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index 35c495c67b..76d6665a9b 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -338,6 +338,7 @@ var shell = { this.contentBrowser.addEventListener('mozbrowserloadstart', this, true); this.contentBrowser.addEventListener('mozbrowserselectionstatechanged', this, true); this.contentBrowser.addEventListener('mozbrowserscrollviewchange', this, true); + this.contentBrowser.addEventListener('mozbrowsercaretstatechanged', this); CustomEventManager.init(); WebappsHelper.init(); @@ -364,6 +365,7 @@ var shell = { this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true); this.contentBrowser.removeEventListener('mozbrowserselectionstatechanged', this, true); this.contentBrowser.removeEventListener('mozbrowserscrollviewchange', this, true); + this.contentBrowser.removeEventListener('mozbrowsercaretstatechanged', this); ppmm.removeMessageListener("content-handler", this); UserAgentOverrides.uninit(); @@ -481,6 +483,28 @@ var shell = { detail: data, }); break; + case 'mozbrowsercaretstatechanged': + { + let elt = evt.target; + let win = elt.ownerDocument.defaultView; + let offsetX = win.mozInnerScreenX - window.mozInnerScreenX; + let offsetY = win.mozInnerScreenY - window.mozInnerScreenY; + + let rect = elt.getBoundingClientRect(); + offsetX += rect.left; + offsetY += rect.top; + + let data = evt.detail; + data.offsetX = offsetX; + data.offsetY = offsetY; + data.sendDoCommandMsg = null; + + shell.sendChromeEvent({ + type: 'caretstatechanged', + detail: data, + }); + } + break; case 'MozApplicationManifest': try { @@ -689,6 +713,10 @@ var CustomEventManager = { case 'do-command': DoCommandHelper.handleEvent(detail.cmd); break; + case 'copypaste-do-command': + Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser }, + 'ask-children-to-execute-copypaste-command', detail.cmd); + break; } } } diff --git a/dom/audiochannel/AudioChannelAgent.cpp b/dom/audiochannel/AudioChannelAgent.cpp index 6fe7038ae6..aaef0c336e 100644 --- a/dom/audiochannel/AudioChannelAgent.cpp +++ b/dom/audiochannel/AudioChannelAgent.cpp @@ -99,7 +99,15 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType, return NS_ERROR_FAILURE; } - mWindow = aWindow; + if (aWindow) { + nsCOMPtr pWindow = do_QueryInterface(aWindow); + if (!pWindow->IsInnerWindow()) { + pWindow = pWindow->GetCurrentInnerWindow(); + } + + mWindow = pWindow.forget(); + } + mAudioChannelType = aChannelType; if (aUseWeakRef) { diff --git a/dom/audiochannel/AudioChannelService.cpp b/dom/audiochannel/AudioChannelService.cpp index 113099084c..87eceb2a8a 100644 --- a/dom/audiochannel/AudioChannelService.cpp +++ b/dom/audiochannel/AudioChannelService.cpp @@ -37,6 +37,19 @@ using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::hal; +// When a inner-window is destroyed we have to mute all the related +// AudioChannelAgents. In order to do this we have to notify them after purging +// AudioChannelService::mAgents. +struct MOZ_STACK_CLASS WindowDestroyedEnumeratorData +{ + explicit WindowDestroyedEnumeratorData(uint64_t aInnerID) + : mInnerID(aInnerID) + {} + + nsTArray> mAgents; + uint64_t mInnerID; +}; + StaticRefPtr gAudioChannelService; // Mappings from 'mozaudiochannel' attribute strings to an enumeration. @@ -360,6 +373,22 @@ AudioChannelService::GetState(AudioChannelAgent* aAgent, bool aElementHidden) data->mState = GetStateInternal(data->mChannel, CONTENT_PROCESS_ID_MAIN, aElementHidden, oldElementHidden); + #ifdef MOZ_WIDGET_GONK + /** Only modify the speaker status when + * (1) apps in the foreground. + * (2) apps in the backgrund and inactive. + * Notice : check the state when the visible status is stable, because there + * has lantency in passing the visibility events. + **/ + bool active = AnyAudioChannelIsActive(); + if (aElementHidden == oldElementHidden && + (!aElementHidden || (aElementHidden && !active))) { + for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { + mSpeakerManager[i]->SetAudioChannelActive(active); + } + } + #endif + return data->mState; } @@ -768,11 +797,15 @@ AudioChannelService::WindowDestroyedEnumerator(AudioChannelAgent* aAgent, nsAutoPtr& aData, void* aPtr) { - uint64_t* innerID = static_cast(aPtr); - MOZ_ASSERT(innerID); + auto* data = static_cast(aPtr); + MOZ_ASSERT(data); nsCOMPtr window = do_QueryInterface(aAgent->Window()); - if (!window || window->WindowID() != *innerID) { + if (window && !window->IsInnerWindow()) { + window = window->GetCurrentInnerWindow(); + } + + if (!window || window->WindowID() != data->mInnerID) { return PL_DHASH_NEXT; } @@ -781,6 +814,7 @@ AudioChannelService::WindowDestroyedEnumerator(AudioChannelAgent* aAgent, service->UnregisterType(aData->mChannel, aData->mElementHidden, CONTENT_PROCESS_ID_MAIN, aData->mWithVideo); + data->mAgents.AppendElement(aAgent); return PL_DHASH_REMOVE; } @@ -886,7 +920,11 @@ AudioChannelService::Observe(nsISupports* aSubject, const char* aTopic, const ch return rv; } - mAgents.Enumerate(WindowDestroyedEnumerator, &innerID); + WindowDestroyedEnumeratorData data(innerID); + mAgents.Enumerate(WindowDestroyedEnumerator, &data); + for (uint32_t i = 0, len = data.mAgents.Length(); i < len; ++i) { + data.mAgents[i]->NotifyAudioChannelStateChanged(); + } #ifdef MOZ_WIDGET_GONK bool active = AnyAudioChannelIsActive(); diff --git a/dom/audiochannel/AudioChannelServiceChild.cpp b/dom/audiochannel/AudioChannelServiceChild.cpp index d60b29f91d..ba23feb8ba 100644 --- a/dom/audiochannel/AudioChannelServiceChild.cpp +++ b/dom/audiochannel/AudioChannelServiceChild.cpp @@ -94,6 +94,22 @@ AudioChannelServiceChild::GetState(AudioChannelAgent* aAgent, bool aElementHidde data->mState = state; cc->SendAudioChannelChangedNotification(); + #ifdef MOZ_WIDGET_GONK + /** Only modify the speaker status when + * (1) apps in the foreground. + * (2) apps in the backgrund and inactive. + * Notice : modify only when the visible status is stable, because there + * has lantency in passing the visibility events. + **/ + bool active = AnyAudioChannelIsActive(); + if (aElementHidden == oldElementHidden && + (!aElementHidden || (aElementHidden && !active))) { + for (uint32_t i = 0; i < mSpeakerManager.Length(); i++) { + mSpeakerManager[i]->SetAudioChannelActive(active); + } + } + #endif + return state; } diff --git a/dom/base/test/mixedcontentblocker/mochitest.ini b/dom/base/test/mixedcontentblocker/mochitest.ini index 82502b2976..c118cf462d 100644 --- a/dom/base/test/mixedcontentblocker/mochitest.ini +++ b/dom/base/test/mixedcontentblocker/mochitest.ini @@ -13,8 +13,8 @@ support-files = file_mixed_content_server.sjs [test_mixed_content_blocker.html] -skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT, SSL_REQUIRED +skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT, SSL_REQUIRED # Bug 1141029 Mulet parity with B2G Desktop for TC [test_mixed_content_blocker_bug803225.html] -skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT, SSL_REQUIRED +skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT, SSL_REQUIRED # Bug 1141029 Mulet parity with B2G Desktop for TC [test_mixed_content_blocker_frameNavigation.html] -skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT, SSL_REQUIRED +skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT, SSL_REQUIRED # Bug 1141029 Mulet parity with B2G Desktop for TC diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 20c1d13318..28702ddce1 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -724,7 +724,7 @@ skip-if = toolkit == 'android' || e10s #RANDOM [test_w3element_traversal.xhtml] [test_w3element_traversal_svg.html] [test_websocket.html] -skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s +skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'android' || e10s # TC: Bug 1144079 - Re-enable Mulet mochitests and reftests taskcluster-specific disables. [test_websocket_basic.html] skip-if = buildapp == 'b2g' || toolkit == 'android' [test_websocket_hello.html] diff --git a/dom/browser-element/BrowserElementChild.js b/dom/browser-element/BrowserElementChild.js index 81747115e4..653c5de604 100644 --- a/dom/browser-element/BrowserElementChild.js +++ b/dom/browser-element/BrowserElementChild.js @@ -34,20 +34,35 @@ function isTopBrowserElement(docShell) { } if (!('BrowserElementIsPreloaded' in this)) { - if (isTopBrowserElement(docShell) && - Services.prefs.getBoolPref("dom.mozInputMethod.enabled")) { - try { - Services.scriptloader.loadSubScript("chrome://global/content/forms.js"); - } catch (e) { + if (isTopBrowserElement(docShell)) { + if (Services.prefs.getBoolPref("dom.mozInputMethod.enabled")) { + try { + Services.scriptloader.loadSubScript("chrome://global/content/forms.js"); + } catch (e) { + } } + + Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js"); } - Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js"); - ContentPanning.init(); + if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") == 1) { + if (docShell.asyncPanZoomEnabled === false) { + Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js"); + ContentPanningAPZDisabled.init(); + } + + Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js"); + ContentPanning.init(); + } Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js"); } else { - ContentPanning.init(); + if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") == 1) { + if (docShell.asyncPanZoomEnabled === false) { + ContentPanningAPZDisabled.init(); + } + ContentPanning.init(); + } } var BrowserElementIsReady = true; diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js index 9b3084138e..157e0526b7 100644 --- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -173,6 +173,11 @@ BrowserElementChild.prototype = { /* useCapture = */ true, /* wantsUntrusted = */ false); + addEventListener('click', + this._ClickHandler.bind(this), + /* useCapture = */ false, + /* wantsUntrusted = */ false); + // This listens to unload events from our message manager, but /not/ from // the |content| window. That's because the window's unload event doesn't // bubble, and we're not using a capturing listener. If we'd used @@ -212,7 +217,10 @@ BrowserElementChild.prototype = { "activate-next-paint-listener": this._activateNextPaintListener.bind(this), "set-input-method-active": this._recvSetInputMethodActive.bind(this), "deactivate-next-paint-listener": this._deactivateNextPaintListener.bind(this), - "do-command": this._recvDoCommand + "do-command": this._recvDoCommand, + "find-all": this._recvFindAll.bind(this), + "find-next": this._recvFindNext.bind(this), + "clear-match": this._recvClearMatch.bind(this), } addMessageListener("browser-element-api:call", function(aMessage) { @@ -523,6 +531,7 @@ BrowserElementChild.prototype = { debug('Got metaChanged: (' + e.target.name + ') ' + e.target.content); let handlers = { + 'viewmode': this._viewmodeChangedHandler, 'theme-color': this._themeColorChangedHandler, 'application-name': this._applicationNameChangedHandler }; @@ -580,6 +589,18 @@ BrowserElementChild.prototype = { sendAsyncMsg('scrollviewchange', detail); }, + _ClickHandler: function(e) { + let elem = e.target; + if (elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) { + // Open in a new tab if middle click or ctrl/cmd-click. + if ((Services.appinfo.OS == 'Darwin' && e.metaKey) || + (Services.appinfo.OS != 'Darwin' && e.ctrlKey) || + e.button == 1) { + sendAsyncMsg('opentab', {url: elem.href}); + } + } + }, + _selectionStateChangedHandler: function(e) { e.stopPropagation(); @@ -671,6 +692,16 @@ BrowserElementChild.prototype = { sendAsyncMsg('selectionstatechanged', detail); }, + + _viewmodeChangedHandler: function(eventType, target) { + let meta = { + name: 'viewmode', + content: target.content, + type: eventType.replace('DOMMeta', '').toLowerCase() + }; + sendAsyncMsg('metachange', meta); + }, + _themeColorChangedHandler: function(eventType, target) { let meta = { name: 'theme-color', @@ -814,21 +845,26 @@ BrowserElementChild.prototype = { }, _getSystemCtxMenuData: function(elem) { + let documentURI = + docShell.QueryInterface(Ci.nsIWebNavigation).currentURI.spec; if ((elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) || (elem instanceof Ci.nsIDOMHTMLAreaElement && elem.href)) { return {uri: elem.href, + documentURI: documentURI, text: elem.textContent.substring(0, kLongestReturnedString)}; } if (elem instanceof Ci.nsIImageLoadingContent && elem.currentURI) { - return {uri: elem.currentURI.spec}; + return {uri: elem.currentURI.spec, documentURI: documentURI}; } if (elem instanceof Ci.nsIDOMHTMLImageElement) { - return {uri: elem.src}; + return {uri: elem.src, documentURI: documentURI}; } if (elem instanceof Ci.nsIDOMHTMLMediaElement) { let hasVideo = !(elem.readyState >= elem.HAVE_METADATA && (elem.videoWidth == 0 || elem.videoHeight == 0)); - return {uri: elem.currentSrc || elem.src, hasVideo: hasVideo}; + return {uri: elem.currentSrc || elem.src, + hasVideo: hasVideo, + documentURI: documentURI}; } if (elem instanceof Ci.nsIDOMHTMLInputElement && elem.hasAttribute("name")) { @@ -845,6 +881,7 @@ BrowserElementChild.prototype = { ? parent.getAttribute("method").toLowerCase() : "get"; return { + documentURI: documentURI, action: actionHref, method: method, name: elem.getAttribute("name"), @@ -1080,7 +1117,7 @@ BrowserElementChild.prototype = { _updateVisibility: function() { var visible = this._forcedVisible && this._ownerVisible; - if (docShell.isActive !== visible) { + if (docShell && docShell.isActive !== visible) { docShell.isActive = visible; sendAsyncMsg('visibilitychange', {visible: visible}); } @@ -1164,6 +1201,57 @@ BrowserElementChild.prototype = { } }, + _initFinder: function() { + if (!this._finder) { + try { + this._findLimit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit"); + } catch (e) { + // Pref not available, assume 0, no match counting. + this._findLimit = 0; + } + + let {Finder} = Components.utils.import("resource://gre/modules/Finder.jsm", {}); + this._finder = new Finder(docShell); + this._finder.addResultListener({ + onMatchesCountResult: (data) => { + sendAsyncMsg('findchange', { + active: true, + searchString: this._finder.searchString, + searchLimit: this._findLimit, + activeMatchOrdinal: data.current, + numberOfMatches: data.total + }); + } + }); + } + }, + + _recvFindAll: function(data) { + this._initFinder(); + let searchString = data.json.searchString; + this._finder.caseSensitive = data.json.caseSensitive; + this._finder.fastFind(searchString, false, false); + this._finder.requestMatchesCount(searchString, this._findLimit, false); + }, + + _recvFindNext: function(data) { + if (!this._finder) { + debug("findNext() called before findAll()"); + return; + } + this._finder.findAgain(data.json.backward, false, false); + this._finder.requestMatchesCount(this._finder.searchString, this._findLimit, false); + }, + + _recvClearMatch: function(data) { + if (!this._finder) { + debug("clearMach() called before findAll()"); + return; + } + this._finder.removeSelection(); + sendAsyncMsg('findchange', {active: false}); + }, + _recvSetInputMethodActive: function(data) { let msgData = { id: data.json.id }; if (!this._isContentWindowCreated) { diff --git a/dom/browser-element/BrowserElementCopyPaste.js b/dom/browser-element/BrowserElementCopyPaste.js new file mode 100644 index 0000000000..bfe4f71bc2 --- /dev/null +++ b/dom/browser-element/BrowserElementCopyPaste.js @@ -0,0 +1,90 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +dump("###################################### BrowserElementCopyPaste.js loaded\n"); + +let CopyPasteAssistent = { + COMMAND_MAP: { + 'cut': 'cmd_cut', + 'copy': 'cmd_copyAndCollapseToEnd', + 'paste': 'cmd_paste', + 'selectall': 'cmd_selectAll' + }, + + init: function() { + addEventListener('mozcaretstatechanged', + this._caretStateChangedHandler.bind(this), + /* useCapture = */ true, + /* wantsUntrusted = */ false); + addMessageListener('browser-element-api:call', this._browserAPIHandler.bind(this)); + }, + + _browserAPIHandler: function(e) { + switch (e.data.msg_name) { + case 'copypaste-do-command': + if (this._isCommandEnabled(e.data.command)) { + docShell.doCommand(COMMAND_MAP[e.data.command]); + } + break; + } + }, + + _isCommandEnabled: function(cmd) { + let command = this.COMMAND_MAP[cmd]; + if (!command) { + return false; + } + + return docShell.isCommandEnabled(command); + }, + + _caretStateChangedHandler: function(e) { + e.stopPropagation(); + + let boundingClientRect = e.boundingClientRect; + let canPaste = this._isCommandEnabled("paste"); + let zoomFactor = content.innerWidth == 0 ? 1 : content.screen.width / content.innerWidth; + + let detail = { + rect: { + width: boundingClientRect ? boundingClientRect.width : 0, + height: boundingClientRect ? boundingClientRect.height : 0, + top: boundingClientRect ? boundingClientRect.top : 0, + bottom: boundingClientRect ? boundingClientRect.bottom : 0, + left: boundingClientRect ? boundingClientRect.left : 0, + right: boundingClientRect ? boundingClientRect.right : 0, + }, + commands: { + canSelectAll: this._isCommandEnabled("selectall"), + canCut: this._isCommandEnabled("cut"), + canCopy: this._isCommandEnabled("copy"), + canPaste: this._isCommandEnabled("paste"), + }, + zoomFactor: zoomFactor, + reason: e.reason, + collapsed: e.collapsed, + caretVisible: e.caretVisible, + selectionVisible: e.selectionVisible + }; + + // Get correct geometry information if we have nested iframe. + let currentWindow = e.target.defaultView; + while (currentWindow.realFrameElement) { + let currentRect = currentWindow.realFrameElement.getBoundingClientRect(); + detail.rect.top += currentRect.top; + detail.rect.bottom += currentRect.top; + detail.rect.left += currentRect.left; + detail.rect.right += currentRect.left; + currentWindow = currentWindow.realFrameElement.ownerDocument.defaultView; + } + + sendAsyncMsg('caretstatechanged', detail); + }, +}; + +CopyPasteAssistent.init(); diff --git a/dom/browser-element/BrowserElementPanning.js b/dom/browser-element/BrowserElementPanning.js index bb7a59d0d0..3800c50557 100644 --- a/dom/browser-element/BrowserElementPanning.js +++ b/dom/browser-element/BrowserElementPanning.js @@ -12,8 +12,6 @@ let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Geometry.jsm"); -var global = this; - const kObservedEvents = [ "BEC:ShownModalPrompt", "Activity:Success", @@ -21,21 +19,7 @@ const kObservedEvents = [ ]; const ContentPanning = { - // Are we listening to touch or mouse events? - watchedEventsType: '', - - // Are mouse events being delivered to this content along with touch - // events, in violation of spec? - hybridEvents: false, - init: function cp_init() { - // If APZ is enabled, we do active element handling in C++ - // (see widget/xpwidgets/ActiveElementManager.h), and panning - // itself in APZ, so we don't need to handle any touch events here. - if (docShell.asyncPanZoomEnabled === false) { - this._setupListenersForPanning(); - } - addEventListener("unload", this._unloadHandler.bind(this), /* useCapture = */ false, @@ -49,444 +33,16 @@ const ContentPanning = { }); }, - _setupListenersForPanning: function cp_setupListenersForPanning() { - let events; - - if (content.TouchEvent) { - events = ['touchstart', 'touchend', 'touchmove']; - this.watchedEventsType = 'touch'; -#ifdef MOZ_WIDGET_GONK - // The gonk widget backend does not deliver mouse events per - // spec. Third-party content isn't exposed to this behavior, - // but that behavior creates some extra work for us here. - let appInfo = Cc["@mozilla.org/xre/app-info;1"]; - let isParentProcess = - !appInfo || appInfo.getService(Ci.nsIXULRuntime) - .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; - this.hybridEvents = isParentProcess; -#endif - } else { - // Touch events aren't supported, so fall back on mouse. - events = ['mousedown', 'mouseup', 'mousemove']; - this.watchedEventsType = 'mouse'; - } - - let els = Cc["@mozilla.org/eventlistenerservice;1"] - .getService(Ci.nsIEventListenerService); - - events.forEach(function(type) { - // Using the system group for mouse/touch events to avoid - // missing events if .stopPropagation() has been called. - els.addSystemEventListener(global, type, - this.handleEvent.bind(this), - /* useCapture = */ false); - }.bind(this)); - }, - - handleEvent: function cp_handleEvent(evt) { - // Ignore events targeting an oop