browser/metro/base/content/contenthandlers/Content.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 // This stays here because otherwise it's hard to tell if there's a parsing error
michael@0 7 dump("### Content.js loaded\n");
michael@0 8
michael@0 9 let Cc = Components.classes;
michael@0 10 let Ci = Components.interfaces;
michael@0 11 let Cu = Components.utils;
michael@0 12 let Cr = Components.results;
michael@0 13
michael@0 14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 15
michael@0 16 XPCOMUtils.defineLazyGetter(this, "Services", function() {
michael@0 17 Cu.import("resource://gre/modules/Services.jsm");
michael@0 18 return Services;
michael@0 19 });
michael@0 20
michael@0 21 XPCOMUtils.defineLazyGetter(this, "Rect", function() {
michael@0 22 Cu.import("resource://gre/modules/Geometry.jsm");
michael@0 23 return Rect;
michael@0 24 });
michael@0 25
michael@0 26 XPCOMUtils.defineLazyGetter(this, "Point", function() {
michael@0 27 Cu.import("resource://gre/modules/Geometry.jsm");
michael@0 28 return Point;
michael@0 29 });
michael@0 30
michael@0 31 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
michael@0 32 "resource://gre/modules/LoginManagerContent.jsm");
michael@0 33
michael@0 34 XPCOMUtils.defineLazyServiceGetter(this, "gFocusManager",
michael@0 35 "@mozilla.org/focus-manager;1", "nsIFocusManager");
michael@0 36
michael@0 37 XPCOMUtils.defineLazyServiceGetter(this, "gDOMUtils",
michael@0 38 "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
michael@0 39
michael@0 40 this.XULDocument = Ci.nsIDOMXULDocument;
michael@0 41 this.HTMLHtmlElement = Ci.nsIDOMHTMLHtmlElement;
michael@0 42 this.HTMLIFrameElement = Ci.nsIDOMHTMLIFrameElement;
michael@0 43 this.HTMLFrameElement = Ci.nsIDOMHTMLFrameElement;
michael@0 44 this.HTMLFrameSetElement = Ci.nsIDOMHTMLFrameSetElement;
michael@0 45 this.HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
michael@0 46 this.HTMLOptionElement = Ci.nsIDOMHTMLOptionElement;
michael@0 47
michael@0 48 const kReferenceDpi = 240; // standard "pixel" size used in some preferences
michael@0 49
michael@0 50 const kStateActive = 0x00000001; // :active pseudoclass for elements
michael@0 51
michael@0 52 const kZoomToElementMargin = 16; // in px
michael@0 53
michael@0 54 /*
michael@0 55 * getBoundingContentRect
michael@0 56 *
michael@0 57 * @param aElement
michael@0 58 * @return Bounding content rect adjusted for scroll and frame offsets.
michael@0 59 */
michael@0 60 function getBoundingContentRect(aElement) {
michael@0 61 if (!aElement)
michael@0 62 return new Rect(0, 0, 0, 0);
michael@0 63
michael@0 64 let document = aElement.ownerDocument;
michael@0 65 while(document.defaultView.frameElement)
michael@0 66 document = document.defaultView.frameElement.ownerDocument;
michael@0 67
michael@0 68 let offset = ContentScroll.getScrollOffset(content);
michael@0 69 offset = new Point(offset.x, offset.y);
michael@0 70
michael@0 71 let r = aElement.getBoundingClientRect();
michael@0 72
michael@0 73 // step out of iframes and frames, offsetting scroll values
michael@0 74 let view = aElement.ownerDocument.defaultView;
michael@0 75 for (let frame = view; frame != content; frame = frame.parent) {
michael@0 76 // adjust client coordinates' origin to be top left of iframe viewport
michael@0 77 let rect = frame.frameElement.getBoundingClientRect();
michael@0 78 let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
michael@0 79 let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
michael@0 80 offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
michael@0 81 }
michael@0 82
michael@0 83 return new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height);
michael@0 84 }
michael@0 85 this.getBoundingContentRect = getBoundingContentRect;
michael@0 86
michael@0 87 /*
michael@0 88 * getOverflowContentBoundingRect
michael@0 89 *
michael@0 90 * @param aElement
michael@0 91 * @return Bounding content rect adjusted for scroll and frame offsets.
michael@0 92 */
michael@0 93
michael@0 94 function getOverflowContentBoundingRect(aElement) {
michael@0 95 let r = getBoundingContentRect(aElement);
michael@0 96
michael@0 97 // If the overflow is hidden don't bother calculating it
michael@0 98 let computedStyle = aElement.ownerDocument.defaultView.getComputedStyle(aElement);
michael@0 99 let blockDisplays = ["block", "inline-block", "list-item"];
michael@0 100 if ((blockDisplays.indexOf(computedStyle.getPropertyValue("display")) != -1 &&
michael@0 101 computedStyle.getPropertyValue("overflow") == "hidden") ||
michael@0 102 aElement instanceof HTMLSelectElement) {
michael@0 103 return r;
michael@0 104 }
michael@0 105
michael@0 106 for (let i = 0; i < aElement.childElementCount; i++) {
michael@0 107 r = r.union(getBoundingContentRect(aElement.children[i]));
michael@0 108 }
michael@0 109
michael@0 110 return r;
michael@0 111 }
michael@0 112 this.getOverflowContentBoundingRect = getOverflowContentBoundingRect;
michael@0 113
michael@0 114 /*
michael@0 115 * Content
michael@0 116 *
michael@0 117 * Browser event receiver for content.
michael@0 118 */
michael@0 119 let Content = {
michael@0 120 _debugEvents: false,
michael@0 121
michael@0 122 get formAssistant() {
michael@0 123 delete this.formAssistant;
michael@0 124 return this.formAssistant = new FormAssistant();
michael@0 125 },
michael@0 126
michael@0 127 init: function init() {
michael@0 128 // Asyncronous messages sent from the browser
michael@0 129 addMessageListener("Browser:Blur", this);
michael@0 130 addMessageListener("Browser:SaveAs", this);
michael@0 131 addMessageListener("Browser:MozApplicationCache:Fetch", this);
michael@0 132 addMessageListener("Browser:SetCharset", this);
michael@0 133 addMessageListener("Browser:CanUnload", this);
michael@0 134 addMessageListener("Browser:PanBegin", this);
michael@0 135 addMessageListener("Gesture:SingleTap", this);
michael@0 136 addMessageListener("Gesture:DoubleTap", this);
michael@0 137
michael@0 138 addEventListener("touchstart", this, false);
michael@0 139 addEventListener("click", this, true);
michael@0 140 addEventListener("keydown", this);
michael@0 141 addEventListener("keyup", this);
michael@0 142
michael@0 143 // Synchronous events caught during the bubbling phase
michael@0 144 addEventListener("MozApplicationManifest", this, false);
michael@0 145 addEventListener("DOMContentLoaded", this, false);
michael@0 146 addEventListener("DOMAutoComplete", this, false);
michael@0 147 addEventListener("DOMFormHasPassword", this, false);
michael@0 148 addEventListener("blur", this, false);
michael@0 149 // Attach a listener to watch for "click" events bubbling up from error
michael@0 150 // pages and other similar page. This lets us fix bugs like 401575 which
michael@0 151 // require error page UI to do privileged things, without letting error
michael@0 152 // pages have any privilege themselves.
michael@0 153 addEventListener("click", this, false);
michael@0 154
michael@0 155 docShell.useGlobalHistory = true;
michael@0 156 },
michael@0 157
michael@0 158 /*******************************************
michael@0 159 * Events
michael@0 160 */
michael@0 161
michael@0 162 handleEvent: function handleEvent(aEvent) {
michael@0 163 if (this._debugEvents) Util.dumpLn("Content:", aEvent.type);
michael@0 164 switch (aEvent.type) {
michael@0 165 case "MozApplicationManifest": {
michael@0 166 let doc = aEvent.originalTarget;
michael@0 167 sendAsyncMessage("Browser:MozApplicationManifest", {
michael@0 168 location: doc.documentURIObject.spec,
michael@0 169 manifest: doc.documentElement.getAttribute("manifest"),
michael@0 170 charset: doc.characterSet
michael@0 171 });
michael@0 172 break;
michael@0 173 }
michael@0 174
michael@0 175 case "keyup":
michael@0 176 // If after a key is pressed we still have no input, then close
michael@0 177 // the autocomplete. Perhaps the user used backspace or delete.
michael@0 178 // Allow down arrow to trigger autofill popup on empty input.
michael@0 179 if ((!aEvent.target.value && aEvent.keyCode != aEvent.DOM_VK_DOWN)
michael@0 180 || aEvent.keyCode == aEvent.DOM_VK_ESCAPE)
michael@0 181 this.formAssistant.close();
michael@0 182 else
michael@0 183 this.formAssistant.open(aEvent.target, aEvent);
michael@0 184 break;
michael@0 185
michael@0 186 case "click":
michael@0 187 // Workaround for bug 925457: we sometimes don't recognize the
michael@0 188 // correct tap target or are unable to identify if it's editable.
michael@0 189 // Instead always save tap co-ordinates for the keyboard to look for
michael@0 190 // when it is up.
michael@0 191 SelectionHandler.onClickCoords(aEvent.clientX, aEvent.clientY);
michael@0 192
michael@0 193 if (aEvent.eventPhase == aEvent.BUBBLING_PHASE)
michael@0 194 this._onClickBubble(aEvent);
michael@0 195 else
michael@0 196 this._onClickCapture(aEvent);
michael@0 197 break;
michael@0 198
michael@0 199 case "DOMFormHasPassword":
michael@0 200 LoginManagerContent.onFormPassword(aEvent);
michael@0 201 break;
michael@0 202
michael@0 203 case "DOMContentLoaded":
michael@0 204 this._maybeNotifyErrorPage();
michael@0 205 break;
michael@0 206
michael@0 207 case "DOMAutoComplete":
michael@0 208 case "blur":
michael@0 209 LoginManagerContent.onUsernameInput(aEvent);
michael@0 210 break;
michael@0 211
michael@0 212 case "touchstart":
michael@0 213 this._onTouchStart(aEvent);
michael@0 214 break;
michael@0 215 }
michael@0 216 },
michael@0 217
michael@0 218 receiveMessage: function receiveMessage(aMessage) {
michael@0 219 if (this._debugEvents) Util.dumpLn("Content:", aMessage.name);
michael@0 220 let json = aMessage.json;
michael@0 221 let x = json.x;
michael@0 222 let y = json.y;
michael@0 223
michael@0 224 switch (aMessage.name) {
michael@0 225 case "Browser:Blur":
michael@0 226 gFocusManager.clearFocus(content);
michael@0 227 break;
michael@0 228
michael@0 229 case "Browser:CanUnload":
michael@0 230 let canUnload = docShell.contentViewer.permitUnload();
michael@0 231 sendSyncMessage("Browser:CanUnload:Return", { permit: canUnload });
michael@0 232 break;
michael@0 233
michael@0 234 case "Browser:SaveAs":
michael@0 235 break;
michael@0 236
michael@0 237 case "Browser:MozApplicationCache:Fetch": {
michael@0 238 let currentURI = Services.io.newURI(json.location, json.charset, null);
michael@0 239 let manifestURI = Services.io.newURI(json.manifest, json.charset, currentURI);
michael@0 240 let updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"]
michael@0 241 .getService(Ci.nsIOfflineCacheUpdateService);
michael@0 242 updateService.scheduleUpdate(manifestURI, currentURI, content);
michael@0 243 break;
michael@0 244 }
michael@0 245
michael@0 246 case "Browser:SetCharset": {
michael@0 247 docShell.gatherCharsetMenuTelemetry();
michael@0 248 docShell.charset = json.charset;
michael@0 249
michael@0 250 let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
michael@0 251 webNav.reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
michael@0 252 break;
michael@0 253 }
michael@0 254
michael@0 255 case "Browser:PanBegin":
michael@0 256 this._cancelTapHighlight();
michael@0 257 break;
michael@0 258
michael@0 259 case "Gesture:SingleTap":
michael@0 260 this._onSingleTap(json.x, json.y, json.modifiers);
michael@0 261 break;
michael@0 262
michael@0 263 case "Gesture:DoubleTap":
michael@0 264 this._onDoubleTap(json.x, json.y);
michael@0 265 break;
michael@0 266 }
michael@0 267 },
michael@0 268
michael@0 269 /******************************************************
michael@0 270 * Event handlers
michael@0 271 */
michael@0 272
michael@0 273 _onTouchStart: function _onTouchStart(aEvent) {
michael@0 274 let element = aEvent.target;
michael@0 275
michael@0 276 // There is no need to have a feedback for disabled element
michael@0 277 let isDisabled = element instanceof HTMLOptionElement ?
michael@0 278 (element.disabled || element.parentNode.disabled) : element.disabled;
michael@0 279 if (isDisabled)
michael@0 280 return;
michael@0 281
michael@0 282 // Set the target element to active
michael@0 283 this._doTapHighlight(element);
michael@0 284 },
michael@0 285
michael@0 286 _onClickCapture: function _onClickCapture(aEvent) {
michael@0 287 let element = aEvent.target;
michael@0 288
michael@0 289 ContextMenuHandler.reset();
michael@0 290
michael@0 291 // Only show autocomplete after the item is clicked
michael@0 292 if (!this.lastClickElement || this.lastClickElement != element) {
michael@0 293 this.lastClickElement = element;
michael@0 294 if (aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE &&
michael@0 295 !(element instanceof HTMLSelectElement)) {
michael@0 296 return;
michael@0 297 }
michael@0 298 }
michael@0 299
michael@0 300 this.formAssistant.focusSync = true;
michael@0 301 this.formAssistant.open(element, aEvent);
michael@0 302 this._cancelTapHighlight();
michael@0 303 this.formAssistant.focusSync = false;
michael@0 304
michael@0 305 // A tap on a form input triggers touch input caret selection
michael@0 306 if (Util.isEditable(element) &&
michael@0 307 aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH) {
michael@0 308 let { offsetX, offsetY } = Util.translateToTopLevelWindow(element);
michael@0 309 sendAsyncMessage("Content:SelectionCaret", {
michael@0 310 xPos: aEvent.clientX + offsetX,
michael@0 311 yPos: aEvent.clientY + offsetY
michael@0 312 });
michael@0 313 } else {
michael@0 314 SelectionHandler.closeSelection();
michael@0 315 }
michael@0 316 },
michael@0 317
michael@0 318 // Checks clicks we care about - events bubbling up from about pages.
michael@0 319 _onClickBubble: function _onClickBubble(aEvent) {
michael@0 320 // Don't trust synthetic events
michael@0 321 if (!aEvent.isTrusted)
michael@0 322 return;
michael@0 323
michael@0 324 let ot = aEvent.originalTarget;
michael@0 325 let errorDoc = ot.ownerDocument;
michael@0 326 if (!errorDoc)
michael@0 327 return;
michael@0 328
michael@0 329 // If the event came from an ssl error page, it is probably either
michael@0 330 // "Add Exception…" or "Get me out of here!" button.
michael@0 331 if (/^about:certerror\?e=nssBadCert/.test(errorDoc.documentURI)) {
michael@0 332 let perm = errorDoc.getElementById("permanentExceptionButton");
michael@0 333 let temp = errorDoc.getElementById("temporaryExceptionButton");
michael@0 334 if (ot == temp || ot == perm) {
michael@0 335 let action = (ot == perm ? "permanent" : "temporary");
michael@0 336 sendAsyncMessage("Browser:CertException",
michael@0 337 { url: errorDoc.location.href, action: action });
michael@0 338 } else if (ot == errorDoc.getElementById("getMeOutOfHereButton")) {
michael@0 339 sendAsyncMessage("Browser:CertException",
michael@0 340 { url: errorDoc.location.href, action: "leave" });
michael@0 341 }
michael@0 342 } else if (/^about:blocked/.test(errorDoc.documentURI)) {
michael@0 343 // The event came from a button on a malware/phishing block page
michael@0 344 // First check whether it's malware or phishing, so that we can
michael@0 345 // use the right strings/links.
michael@0 346 let isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
michael@0 347
michael@0 348 if (ot == errorDoc.getElementById("getMeOutButton")) {
michael@0 349 sendAsyncMessage("Browser:BlockedSite",
michael@0 350 { url: errorDoc.location.href, action: "leave" });
michael@0 351 } else if (ot == errorDoc.getElementById("reportButton")) {
michael@0 352 // This is the "Why is this site blocked" button. For malware,
michael@0 353 // we can fetch a site-specific report, for phishing, we redirect
michael@0 354 // to the generic page describing phishing protection.
michael@0 355 let action = isMalware ? "report-malware" : "report-phishing";
michael@0 356 sendAsyncMessage("Browser:BlockedSite",
michael@0 357 { url: errorDoc.location.href, action: action });
michael@0 358 } else if (ot == errorDoc.getElementById("ignoreWarningButton")) {
michael@0 359 // Allow users to override and continue through to the site,
michael@0 360 // but add a notify bar as a reminder, so that they don't lose
michael@0 361 // track after, e.g., tab switching.
michael@0 362 let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
michael@0 363 webNav.loadURI(content.location,
michael@0 364 Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
michael@0 365 null, null, null);
michael@0 366 }
michael@0 367 }
michael@0 368 },
michael@0 369
michael@0 370 _onSingleTap: function (aX, aY, aModifiers) {
michael@0 371 let utils = Util.getWindowUtils(content);
michael@0 372 for (let type of ["mousemove", "mousedown", "mouseup"]) {
michael@0 373 utils.sendMouseEventToWindow(type, aX, aY, 0, 1, aModifiers, true, 1.0,
michael@0 374 Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
michael@0 375 }
michael@0 376 },
michael@0 377
michael@0 378 _onDoubleTap: function (aX, aY) {
michael@0 379 let { element } = Content.getCurrentWindowAndOffset(aX, aY);
michael@0 380 while (element && !this._shouldZoomToElement(element)) {
michael@0 381 element = element.parentNode;
michael@0 382 }
michael@0 383
michael@0 384 if (!element) {
michael@0 385 this._zoomOut();
michael@0 386 } else {
michael@0 387 this._zoomToElement(element);
michael@0 388 }
michael@0 389 },
michael@0 390
michael@0 391 /******************************************************
michael@0 392 * Zoom utilities
michael@0 393 */
michael@0 394 _zoomOut: function() {
michael@0 395 let rect = new Rect(0,0,0,0);
michael@0 396 this._zoomToRect(rect);
michael@0 397 },
michael@0 398
michael@0 399 _zoomToElement: function(aElement) {
michael@0 400 let rect = getBoundingContentRect(aElement);
michael@0 401 this._inflateRect(rect, kZoomToElementMargin);
michael@0 402 this._zoomToRect(rect);
michael@0 403 },
michael@0 404
michael@0 405 _inflateRect: function(aRect, aMargin) {
michael@0 406 aRect.left -= aMargin;
michael@0 407 aRect.top -= aMargin;
michael@0 408 aRect.bottom += aMargin;
michael@0 409 aRect.right += aMargin;
michael@0 410 },
michael@0 411
michael@0 412 _zoomToRect: function (aRect) {
michael@0 413 let utils = Util.getWindowUtils(content);
michael@0 414 let viewId = utils.getViewId(content.document.documentElement);
michael@0 415 let presShellId = {};
michael@0 416 utils.getPresShellId(presShellId);
michael@0 417 sendAsyncMessage("Content:ZoomToRect", {
michael@0 418 rect: aRect,
michael@0 419 presShellId: presShellId.value,
michael@0 420 viewId: viewId,
michael@0 421 });
michael@0 422 },
michael@0 423
michael@0 424 _shouldZoomToElement: function(aElement) {
michael@0 425 let win = aElement.ownerDocument.defaultView;
michael@0 426 if (win.getComputedStyle(aElement, null).display == "inline") {
michael@0 427 return false;
michael@0 428 }
michael@0 429 else if (aElement instanceof Ci.nsIDOMHTMLLIElement) {
michael@0 430 return false;
michael@0 431 }
michael@0 432 else if (aElement instanceof Ci.nsIDOMHTMLQuoteElement) {
michael@0 433 return false;
michael@0 434 }
michael@0 435 else {
michael@0 436 return true;
michael@0 437 }
michael@0 438 },
michael@0 439
michael@0 440
michael@0 441 /******************************************************
michael@0 442 * General utilities
michael@0 443 */
michael@0 444
michael@0 445 /*
michael@0 446 * Retrieve the total offset from the window's origin to the sub frame
michael@0 447 * element including frame and scroll offsets. The resulting offset is
michael@0 448 * such that:
michael@0 449 * sub frame coords + offset = root frame position
michael@0 450 */
michael@0 451 getCurrentWindowAndOffset: function(x, y) {
michael@0 452 // If the element at the given point belongs to another document (such
michael@0 453 // as an iframe's subdocument), the element in the calling document's
michael@0 454 // DOM (e.g. the iframe) is returned.
michael@0 455 let utils = Util.getWindowUtils(content);
michael@0 456 let element = utils.elementFromPoint(x, y, true, false);
michael@0 457 let offset = { x:0, y:0 };
michael@0 458
michael@0 459 while (element && (element instanceof HTMLIFrameElement ||
michael@0 460 element instanceof HTMLFrameElement)) {
michael@0 461 // get the child frame position in client coordinates
michael@0 462 let rect = element.getBoundingClientRect();
michael@0 463
michael@0 464 // calculate offsets for digging down into sub frames
michael@0 465 // using elementFromPoint:
michael@0 466
michael@0 467 // Get the content scroll offset in the child frame
michael@0 468 scrollOffset = ContentScroll.getScrollOffset(element.contentDocument.defaultView);
michael@0 469 // subtract frame and scroll offset from our elementFromPoint coordinates
michael@0 470 x -= rect.left + scrollOffset.x;
michael@0 471 y -= rect.top + scrollOffset.y;
michael@0 472
michael@0 473 // calculate offsets we'll use to translate to client coords:
michael@0 474
michael@0 475 // add frame client offset to our total offset result
michael@0 476 offset.x += rect.left;
michael@0 477 offset.y += rect.top;
michael@0 478
michael@0 479 // get the frame's nsIDOMWindowUtils
michael@0 480 utils = element.contentDocument
michael@0 481 .defaultView
michael@0 482 .QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 483 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 484
michael@0 485 // retrieve the target element in the sub frame at x, y
michael@0 486 element = utils.elementFromPoint(x, y, true, false);
michael@0 487 }
michael@0 488
michael@0 489 if (!element)
michael@0 490 return {};
michael@0 491
michael@0 492 return {
michael@0 493 element: element,
michael@0 494 contentWindow: element.ownerDocument.defaultView,
michael@0 495 offset: offset,
michael@0 496 utils: utils
michael@0 497 };
michael@0 498 },
michael@0 499
michael@0 500
michael@0 501 _maybeNotifyErrorPage: function _maybeNotifyErrorPage() {
michael@0 502 // Notify browser that an error page is being shown instead
michael@0 503 // of the target location. Necessary to get proper thumbnail
michael@0 504 // updates on chrome for error pages.
michael@0 505 if (content.location.href !== content.document.documentURI)
michael@0 506 sendAsyncMessage("Browser:ErrorPage", null);
michael@0 507 },
michael@0 508
michael@0 509 _highlightElement: null,
michael@0 510
michael@0 511 _doTapHighlight: function _doTapHighlight(aElement) {
michael@0 512 gDOMUtils.setContentState(aElement, kStateActive);
michael@0 513 this._highlightElement = aElement;
michael@0 514 },
michael@0 515
michael@0 516 _cancelTapHighlight: function _cancelTapHighlight(aElement) {
michael@0 517 gDOMUtils.setContentState(content.document.documentElement, kStateActive);
michael@0 518 this._highlightElement = null;
michael@0 519 },
michael@0 520 };
michael@0 521
michael@0 522 Content.init();
michael@0 523
michael@0 524 var FormSubmitObserver = {
michael@0 525 init: function init(){
michael@0 526 addMessageListener("Browser:TabOpen", this);
michael@0 527 addMessageListener("Browser:TabClose", this);
michael@0 528
michael@0 529 addEventListener("pageshow", this, false);
michael@0 530
michael@0 531 Services.obs.addObserver(this, "invalidformsubmit", false);
michael@0 532 },
michael@0 533
michael@0 534 handleEvent: function handleEvent(aEvent) {
michael@0 535 let target = aEvent.originalTarget;
michael@0 536 let isRootDocument = (target == content.document || target.ownerDocument == content.document);
michael@0 537 if (!isRootDocument)
michael@0 538 return;
michael@0 539
michael@0 540 // Reset invalid submit state on each pageshow
michael@0 541 if (aEvent.type == "pageshow")
michael@0 542 Content.formAssistant.invalidSubmit = false;
michael@0 543 },
michael@0 544
michael@0 545 receiveMessage: function receiveMessage(aMessage) {
michael@0 546 let json = aMessage.json;
michael@0 547 switch (aMessage.name) {
michael@0 548 case "Browser:TabOpen":
michael@0 549 Services.obs.addObserver(this, "formsubmit", false);
michael@0 550 break;
michael@0 551 case "Browser:TabClose":
michael@0 552 Services.obs.removeObserver(this, "formsubmit");
michael@0 553 break;
michael@0 554 }
michael@0 555 },
michael@0 556
michael@0 557 notify: function notify(aFormElement, aWindow, aActionURI, aCancelSubmit) {
michael@0 558 // Do not notify unless this is the window where the submit occurred
michael@0 559 if (aWindow == content)
michael@0 560 // We don't need to send any data along
michael@0 561 sendAsyncMessage("Browser:FormSubmit", {});
michael@0 562 },
michael@0 563
michael@0 564 notifyInvalidSubmit: function notifyInvalidSubmit(aFormElement, aInvalidElements) {
michael@0 565 if (!aInvalidElements.length)
michael@0 566 return;
michael@0 567
michael@0 568 let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
michael@0 569 if (!(element instanceof HTMLInputElement ||
michael@0 570 element instanceof HTMLTextAreaElement ||
michael@0 571 element instanceof HTMLSelectElement ||
michael@0 572 element instanceof HTMLButtonElement)) {
michael@0 573 return;
michael@0 574 }
michael@0 575
michael@0 576 Content.formAssistant.invalidSubmit = true;
michael@0 577 Content.formAssistant.open(element);
michael@0 578 },
michael@0 579
michael@0 580 QueryInterface : function(aIID) {
michael@0 581 if (!aIID.equals(Ci.nsIFormSubmitObserver) &&
michael@0 582 !aIID.equals(Ci.nsISupportsWeakReference) &&
michael@0 583 !aIID.equals(Ci.nsISupports))
michael@0 584 throw Cr.NS_ERROR_NO_INTERFACE;
michael@0 585 return this;
michael@0 586 }
michael@0 587 };
michael@0 588 this.Content = Content;
michael@0 589
michael@0 590 FormSubmitObserver.init();

mercurial