browser/metro/base/content/bindings/browser.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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 let Cc = Components.classes;
michael@0 7 let Ci = Components.interfaces;
michael@0 8 let Cu = Components.utils;
michael@0 9
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11 Cu.import("resource://gre/modules/FormData.jsm");
michael@0 12 Cu.import("resource://gre/modules/ScrollPosition.jsm");
michael@0 13 Cu.import("resource://gre/modules/Timer.jsm", this);
michael@0 14
michael@0 15 let WebProgressListener = {
michael@0 16 _lastLocation: null,
michael@0 17 _firstPaint: false,
michael@0 18
michael@0 19 init: function() {
michael@0 20 let flags = Ci.nsIWebProgress.NOTIFY_LOCATION |
michael@0 21 Ci.nsIWebProgress.NOTIFY_SECURITY |
michael@0 22 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW |
michael@0 23 Ci.nsIWebProgress.NOTIFY_STATE_NETWORK |
michael@0 24 Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT;
michael@0 25
michael@0 26 let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress);
michael@0 27 webProgress.addProgressListener(this, flags);
michael@0 28 },
michael@0 29
michael@0 30 onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
michael@0 31 if (content != aWebProgress.DOMWindow)
michael@0 32 return;
michael@0 33
michael@0 34 sendAsyncMessage("Content:StateChange", {
michael@0 35 contentWindowId: this.contentWindowId,
michael@0 36 stateFlags: aStateFlags,
michael@0 37 status: aStatus
michael@0 38 });
michael@0 39 },
michael@0 40
michael@0 41 onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
michael@0 42 if (content != aWebProgress.DOMWindow)
michael@0 43 return;
michael@0 44
michael@0 45 let spec = aLocationURI ? aLocationURI.spec : "";
michael@0 46 let location = spec.split("#")[0];
michael@0 47
michael@0 48 let charset = content.document.characterSet;
michael@0 49
michael@0 50 sendAsyncMessage("Content:LocationChange", {
michael@0 51 contentWindowId: this.contentWindowId,
michael@0 52 documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec,
michael@0 53 location: spec,
michael@0 54 canGoBack: docShell.canGoBack,
michael@0 55 canGoForward: docShell.canGoForward,
michael@0 56 charset: charset.toString()
michael@0 57 });
michael@0 58
michael@0 59 this._firstPaint = false;
michael@0 60 let self = this;
michael@0 61
michael@0 62 // Keep track of hash changes
michael@0 63 this.hashChanged = (location == this._lastLocation);
michael@0 64 this._lastLocation = location;
michael@0 65
michael@0 66 // When a new page is loaded fire a message for the first paint
michael@0 67 addEventListener("MozAfterPaint", function(aEvent) {
michael@0 68 removeEventListener("MozAfterPaint", arguments.callee, true);
michael@0 69
michael@0 70 self._firstPaint = true;
michael@0 71 let scrollOffset = ContentScroll.getScrollOffset(content);
michael@0 72 sendAsyncMessage("Browser:FirstPaint", scrollOffset);
michael@0 73 }, true);
michael@0 74 },
michael@0 75
michael@0 76 onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
michael@0 77 },
michael@0 78
michael@0 79 onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
michael@0 80 if (content != aWebProgress.DOMWindow)
michael@0 81 return;
michael@0 82
michael@0 83 let serialization = SecurityUI.getSSLStatusAsString();
michael@0 84
michael@0 85 sendAsyncMessage("Content:SecurityChange", {
michael@0 86 contentWindowId: this.contentWindowId,
michael@0 87 SSLStatusAsString: serialization,
michael@0 88 state: aState
michael@0 89 });
michael@0 90 },
michael@0 91
michael@0 92 get contentWindowId() {
michael@0 93 return content.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 94 .getInterface(Ci.nsIDOMWindowUtils)
michael@0 95 .currentInnerWindowID;
michael@0 96 },
michael@0 97
michael@0 98 QueryInterface: function QueryInterface(aIID) {
michael@0 99 if (aIID.equals(Ci.nsIWebProgressListener) ||
michael@0 100 aIID.equals(Ci.nsISupportsWeakReference) ||
michael@0 101 aIID.equals(Ci.nsISupports)) {
michael@0 102 return this;
michael@0 103 }
michael@0 104
michael@0 105 throw Components.results.NS_ERROR_NO_INTERFACE;
michael@0 106 }
michael@0 107 };
michael@0 108
michael@0 109 WebProgressListener.init();
michael@0 110
michael@0 111
michael@0 112 let SecurityUI = {
michael@0 113 getSSLStatusAsString: function() {
michael@0 114 let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
michael@0 115
michael@0 116 if (status) {
michael@0 117 let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
michael@0 118 .getService(Ci.nsISerializationHelper);
michael@0 119
michael@0 120 status.QueryInterface(Ci.nsISerializable);
michael@0 121 return serhelper.serializeToString(status);
michael@0 122 }
michael@0 123
michael@0 124 return null;
michael@0 125 }
michael@0 126 };
michael@0 127
michael@0 128 let WebNavigation = {
michael@0 129 _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation),
michael@0 130 _timer: null,
michael@0 131
michael@0 132 init: function() {
michael@0 133 addMessageListener("WebNavigation:GoBack", this);
michael@0 134 addMessageListener("WebNavigation:GoForward", this);
michael@0 135 addMessageListener("WebNavigation:GotoIndex", this);
michael@0 136 addMessageListener("WebNavigation:LoadURI", this);
michael@0 137 addMessageListener("WebNavigation:Reload", this);
michael@0 138 addMessageListener("WebNavigation:Stop", this);
michael@0 139 },
michael@0 140
michael@0 141 receiveMessage: function(message) {
michael@0 142 switch (message.name) {
michael@0 143 case "WebNavigation:GoBack":
michael@0 144 this.goBack();
michael@0 145 break;
michael@0 146 case "WebNavigation:GoForward":
michael@0 147 this.goForward();
michael@0 148 break;
michael@0 149 case "WebNavigation:GotoIndex":
michael@0 150 this.gotoIndex(message);
michael@0 151 break;
michael@0 152 case "WebNavigation:LoadURI":
michael@0 153 this.loadURI(message);
michael@0 154 break;
michael@0 155 case "WebNavigation:Reload":
michael@0 156 this.reload(message);
michael@0 157 break;
michael@0 158 case "WebNavigation:Stop":
michael@0 159 this.stop(message);
michael@0 160 break;
michael@0 161 }
michael@0 162 },
michael@0 163
michael@0 164 goBack: function() {
michael@0 165 if (this._webNavigation.canGoBack)
michael@0 166 this._webNavigation.goBack();
michael@0 167 },
michael@0 168
michael@0 169 goForward: function() {
michael@0 170 if (this._webNavigation.canGoForward)
michael@0 171 this._webNavigation.goForward();
michael@0 172 },
michael@0 173
michael@0 174 gotoIndex: function(message) {
michael@0 175 this._webNavigation.gotoIndex(message.index);
michael@0 176 },
michael@0 177
michael@0 178 loadURI: function(message) {
michael@0 179 let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE;
michael@0 180 this._webNavigation.loadURI(message.json.uri, flags, null, null, null);
michael@0 181
michael@0 182 let tabData = message.json;
michael@0 183 if (tabData.entries) {
michael@0 184 // We are going to load from history so kill the current load. We do not
michael@0 185 // want the load added to the history anyway. We reload after resetting history
michael@0 186 this._webNavigation.stop(this._webNavigation.STOP_ALL);
michael@0 187 this._restoreHistory(tabData, 0);
michael@0 188 }
michael@0 189 },
michael@0 190
michael@0 191 reload: function(message) {
michael@0 192 let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE;
michael@0 193 this._webNavigation.reload(flags);
michael@0 194 },
michael@0 195
michael@0 196 stop: function(message) {
michael@0 197 let flags = message.json.flags || this._webNavigation.STOP_ALL;
michael@0 198 this._webNavigation.stop(flags);
michael@0 199 },
michael@0 200
michael@0 201 _restoreHistory: function _restoreHistory(aTabData, aCount) {
michael@0 202 // We need to wait for the sessionHistory to be initialized and there
michael@0 203 // is no good way to do this. We'll try a wait loop like desktop
michael@0 204 try {
michael@0 205 if (!this._webNavigation.sessionHistory)
michael@0 206 throw new Error();
michael@0 207 } catch (ex) {
michael@0 208 if (aCount < 10) {
michael@0 209 let self = this;
michael@0 210 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 211 this._timer.initWithCallback(function(aTimer) {
michael@0 212 self._timer = null;
michael@0 213 self._restoreHistory(aTabData, aCount + 1);
michael@0 214 }, 100, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 215 return;
michael@0 216 }
michael@0 217 }
michael@0 218
michael@0 219 let history = this._webNavigation.sessionHistory;
michael@0 220 if (history.count > 0)
michael@0 221 history.PurgeHistory(history.count);
michael@0 222 history.QueryInterface(Ci.nsISHistoryInternal);
michael@0 223
michael@0 224 // helper hashes for ensuring unique frame IDs and unique document
michael@0 225 // identifiers.
michael@0 226 let idMap = { used: {} };
michael@0 227 let docIdentMap = {};
michael@0 228
michael@0 229 for (let i = 0; i < aTabData.entries.length; i++) {
michael@0 230 if (!aTabData.entries[i].url)
michael@0 231 continue;
michael@0 232 history.addEntry(this._deserializeHistoryEntry(aTabData.entries[i], idMap, docIdentMap), true);
michael@0 233 }
michael@0 234
michael@0 235 // We need to force set the active history item and cause it to reload since
michael@0 236 // we stop the load above
michael@0 237 let activeIndex = (aTabData.index || aTabData.entries.length) - 1;
michael@0 238 history.getEntryAtIndex(activeIndex, true);
michael@0 239 history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry();
michael@0 240 },
michael@0 241
michael@0 242 _deserializeHistoryEntry: function _deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
michael@0 243 let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry);
michael@0 244
michael@0 245 shEntry.setURI(Services.io.newURI(aEntry.url, null, null));
michael@0 246 shEntry.setTitle(aEntry.title || aEntry.url);
michael@0 247 if (aEntry.subframe)
michael@0 248 shEntry.setIsSubFrame(aEntry.subframe || false);
michael@0 249 shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
michael@0 250 if (aEntry.contentType)
michael@0 251 shEntry.contentType = aEntry.contentType;
michael@0 252 if (aEntry.referrer)
michael@0 253 shEntry.referrerURI = Services.io.newURI(aEntry.referrer, null, null);
michael@0 254
michael@0 255 if (aEntry.cacheKey) {
michael@0 256 let cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32);
michael@0 257 cacheKey.data = aEntry.cacheKey;
michael@0 258 shEntry.cacheKey = cacheKey;
michael@0 259 }
michael@0 260
michael@0 261 if (aEntry.ID) {
michael@0 262 // get a new unique ID for this frame (since the one from the last
michael@0 263 // start might already be in use)
michael@0 264 let id = aIdMap[aEntry.ID] || 0;
michael@0 265 if (!id) {
michael@0 266 for (id = Date.now(); id in aIdMap.used; id++);
michael@0 267 aIdMap[aEntry.ID] = id;
michael@0 268 aIdMap.used[id] = true;
michael@0 269 }
michael@0 270 shEntry.ID = id;
michael@0 271 }
michael@0 272
michael@0 273 if (aEntry.docshellID)
michael@0 274 shEntry.docshellID = aEntry.docshellID;
michael@0 275
michael@0 276 if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
michael@0 277 shEntry.stateData =
michael@0 278 Cc["@mozilla.org/docshell/structured-clone-container;1"].
michael@0 279 createInstance(Ci.nsIStructuredCloneContainer);
michael@0 280
michael@0 281 shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion);
michael@0 282 }
michael@0 283
michael@0 284 if (aEntry.scroll) {
michael@0 285 let scrollPos = aEntry.scroll.split(",");
michael@0 286 scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
michael@0 287 shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
michael@0 288 }
michael@0 289
michael@0 290 let childDocIdents = {};
michael@0 291 if (aEntry.docIdentifier) {
michael@0 292 // If we have a serialized document identifier, try to find an SHEntry
michael@0 293 // which matches that doc identifier and adopt that SHEntry's
michael@0 294 // BFCacheEntry. If we don't find a match, insert shEntry as the match
michael@0 295 // for the document identifier.
michael@0 296 let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
michael@0 297 if (!matchingEntry) {
michael@0 298 matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
michael@0 299 aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
michael@0 300 } else {
michael@0 301 shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
michael@0 302 childDocIdents = matchingEntry.childDocIdents;
michael@0 303 }
michael@0 304 }
michael@0 305
michael@0 306 if (aEntry.owner_b64) {
michael@0 307 let ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
michael@0 308 let binaryData = atob(aEntry.owner_b64);
michael@0 309 ownerInput.setData(binaryData, binaryData.length);
michael@0 310 let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIObjectInputStream);
michael@0 311 binaryStream.setInputStream(ownerInput);
michael@0 312 try { // Catch possible deserialization exceptions
michael@0 313 shEntry.owner = binaryStream.readObject(true);
michael@0 314 } catch (ex) { dump(ex); }
michael@0 315 }
michael@0 316
michael@0 317 if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
michael@0 318 for (let i = 0; i < aEntry.children.length; i++) {
michael@0 319 if (!aEntry.children[i].url)
michael@0 320 continue;
michael@0 321
michael@0 322 // We're getting sessionrestore.js files with a cycle in the
michael@0 323 // doc-identifier graph, likely due to bug 698656. (That is, we have
michael@0 324 // an entry where doc identifier A is an ancestor of doc identifier B,
michael@0 325 // and another entry where doc identifier B is an ancestor of A.)
michael@0 326 //
michael@0 327 // If we were to respect these doc identifiers, we'd create a cycle in
michael@0 328 // the SHEntries themselves, which causes the docshell to loop forever
michael@0 329 // when it looks for the root SHEntry.
michael@0 330 //
michael@0 331 // So as a hack to fix this, we restrict the scope of a doc identifier
michael@0 332 // to be a node's siblings and cousins, and pass childDocIdents, not
michael@0 333 // aDocIdents, to _deserializeHistoryEntry. That is, we say that two
michael@0 334 // SHEntries with the same doc identifier have the same document iff
michael@0 335 // they have the same parent or their parents have the same document.
michael@0 336
michael@0 337 shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i);
michael@0 338 }
michael@0 339 }
michael@0 340
michael@0 341 return shEntry;
michael@0 342 },
michael@0 343
michael@0 344 sendHistory: function sendHistory() {
michael@0 345 // We need to package up the session history and send it to the sessionstore
michael@0 346 let entries = [];
michael@0 347 let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
michael@0 348 for (let i = 0; i < history.count; i++) {
michael@0 349 let entry = this._serializeHistoryEntry(history.getEntryAtIndex(i, false));
michael@0 350
michael@0 351 // If someone directly navigates to one of these URLs and they switch to Desktop,
michael@0 352 // we need to make the page load-able.
michael@0 353 if (entry.url == "about:home" || entry.url == "about:start") {
michael@0 354 entry.url = "about:newtab";
michael@0 355 }
michael@0 356 entries.push(entry);
michael@0 357 }
michael@0 358 let index = history.index + 1;
michael@0 359 sendAsyncMessage("Content:SessionHistory", { entries: entries, index: index });
michael@0 360 },
michael@0 361
michael@0 362 _serializeHistoryEntry: function _serializeHistoryEntry(aEntry) {
michael@0 363 let entry = { url: aEntry.URI.spec };
michael@0 364
michael@0 365 if (Util.isURLEmpty(entry.url)) {
michael@0 366 entry.title = Util.getEmptyURLTabTitle();
michael@0 367 } else {
michael@0 368 entry.title = aEntry.title;
michael@0 369 }
michael@0 370
michael@0 371 if (!(aEntry instanceof Ci.nsISHEntry))
michael@0 372 return entry;
michael@0 373
michael@0 374 let cacheKey = aEntry.cacheKey;
michael@0 375 if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0)
michael@0 376 entry.cacheKey = cacheKey.data;
michael@0 377
michael@0 378 entry.ID = aEntry.ID;
michael@0 379 entry.docshellID = aEntry.docshellID;
michael@0 380
michael@0 381 if (aEntry.referrerURI)
michael@0 382 entry.referrer = aEntry.referrerURI.spec;
michael@0 383
michael@0 384 if (aEntry.contentType)
michael@0 385 entry.contentType = aEntry.contentType;
michael@0 386
michael@0 387 let x = {}, y = {};
michael@0 388 aEntry.getScrollPosition(x, y);
michael@0 389 if (x.value != 0 || y.value != 0)
michael@0 390 entry.scroll = x.value + "," + y.value;
michael@0 391
michael@0 392 if (aEntry.owner) {
michael@0 393 try {
michael@0 394 let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIObjectOutputStream);
michael@0 395 let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
michael@0 396 pipe.init(false, false, 0, 0xffffffff, null);
michael@0 397 binaryStream.setOutputStream(pipe.outputStream);
michael@0 398 binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
michael@0 399 binaryStream.close();
michael@0 400
michael@0 401 // Now we want to read the data from the pipe's input end and encode it.
michael@0 402 let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
michael@0 403 scriptableStream.setInputStream(pipe.inputStream);
michael@0 404 let ownerBytes = scriptableStream.readByteArray(scriptableStream.available());
michael@0 405 // We can stop doing base64 encoding once our serialization into JSON
michael@0 406 // is guaranteed to handle all chars in strings, including embedded
michael@0 407 // nulls.
michael@0 408 entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
michael@0 409 } catch (e) { dump(e); }
michael@0 410 }
michael@0 411
michael@0 412 entry.docIdentifier = aEntry.BFCacheEntry.ID;
michael@0 413
michael@0 414 if (aEntry.stateData != null) {
michael@0 415 entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
michael@0 416 entry.structuredCloneVersion = aEntry.stateData.formatVersion;
michael@0 417 }
michael@0 418
michael@0 419 if (!(aEntry instanceof Ci.nsISHContainer))
michael@0 420 return entry;
michael@0 421
michael@0 422 if (aEntry.childCount > 0) {
michael@0 423 entry.children = [];
michael@0 424 for (let i = 0; i < aEntry.childCount; i++) {
michael@0 425 let child = aEntry.GetChildAt(i);
michael@0 426 if (child)
michael@0 427 entry.children.push(this._serializeHistoryEntry(child));
michael@0 428 else // to maintain the correct frame order, insert a dummy entry
michael@0 429 entry.children.push({ url: "about:blank" });
michael@0 430
michael@0 431 // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
michael@0 432 if (/^wyciwyg:\/\//.test(entry.children[i].url)) {
michael@0 433 delete entry.children;
michael@0 434 break;
michael@0 435 }
michael@0 436 }
michael@0 437 }
michael@0 438
michael@0 439 return entry;
michael@0 440 }
michael@0 441 };
michael@0 442
michael@0 443 WebNavigation.init();
michael@0 444
michael@0 445
michael@0 446 let DOMEvents = {
michael@0 447 _timeout: null,
michael@0 448 _sessionEvents: new Set(),
michael@0 449 _sessionEventMap: {"SessionStore:collectFormdata" : FormData.collect,
michael@0 450 "SessionStore:collectScrollPosition" : ScrollPosition.collect},
michael@0 451
michael@0 452 init: function() {
michael@0 453 addEventListener("DOMContentLoaded", this, false);
michael@0 454 addEventListener("DOMTitleChanged", this, false);
michael@0 455 addEventListener("DOMLinkAdded", this, false);
michael@0 456 addEventListener("DOMWillOpenModalDialog", this, false);
michael@0 457 addEventListener("DOMModalDialogClosed", this, true);
michael@0 458 addEventListener("DOMWindowClose", this, false);
michael@0 459 addEventListener("DOMPopupBlocked", this, false);
michael@0 460 addEventListener("pageshow", this, false);
michael@0 461 addEventListener("pagehide", this, false);
michael@0 462
michael@0 463 addEventListener("input", this, true);
michael@0 464 addEventListener("change", this, true);
michael@0 465 addEventListener("scroll", this, true);
michael@0 466 addMessageListener("SessionStore:restoreSessionTabData", this);
michael@0 467 },
michael@0 468
michael@0 469 receiveMessage: function(message) {
michael@0 470 switch (message.name) {
michael@0 471 case "SessionStore:restoreSessionTabData":
michael@0 472 if (message.json.formdata)
michael@0 473 FormData.restore(content, message.json.formdata);
michael@0 474 if (message.json.scroll)
michael@0 475 ScrollPosition.restore(content, message.json.scroll.scroll);
michael@0 476 break;
michael@0 477 }
michael@0 478 },
michael@0 479
michael@0 480 handleEvent: function(aEvent) {
michael@0 481 let document = content.document;
michael@0 482 switch (aEvent.type) {
michael@0 483 case "DOMContentLoaded":
michael@0 484 if (document.documentURIObject.spec == "about:blank")
michael@0 485 return;
michael@0 486
michael@0 487 sendAsyncMessage("DOMContentLoaded", { });
michael@0 488
michael@0 489 // Send the session history now too
michael@0 490 WebNavigation.sendHistory();
michael@0 491 break;
michael@0 492
michael@0 493 case "pageshow":
michael@0 494 case "pagehide": {
michael@0 495 if (aEvent.target.defaultView != content)
michael@0 496 break;
michael@0 497
michael@0 498 let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 499 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 500
michael@0 501 let json = {
michael@0 502 contentWindowWidth: content.innerWidth,
michael@0 503 contentWindowHeight: content.innerHeight,
michael@0 504 windowId: util.outerWindowID,
michael@0 505 persisted: aEvent.persisted
michael@0 506 };
michael@0 507
michael@0 508 // Clear onload focus to prevent the VKB to be shown unexpectingly
michael@0 509 // but only if the location has really changed and not only the
michael@0 510 // fragment identifier
michael@0 511 let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
michael@0 512 if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) {
michael@0 513 let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
michael@0 514 focusManager.clearFocus(content);
michael@0 515 }
michael@0 516
michael@0 517 sendAsyncMessage(aEvent.type, json);
michael@0 518 break;
michael@0 519 }
michael@0 520
michael@0 521 case "DOMPopupBlocked": {
michael@0 522 let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 523 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 524 let json = {
michael@0 525 windowId: util.outerWindowID,
michael@0 526 popupWindowURI: {
michael@0 527 spec: aEvent.popupWindowURI.spec,
michael@0 528 charset: aEvent.popupWindowURI.originCharset
michael@0 529 },
michael@0 530 popupWindowFeatures: aEvent.popupWindowFeatures,
michael@0 531 popupWindowName: aEvent.popupWindowName
michael@0 532 };
michael@0 533
michael@0 534 sendAsyncMessage("DOMPopupBlocked", json);
michael@0 535 break;
michael@0 536 }
michael@0 537
michael@0 538 case "DOMTitleChanged":
michael@0 539 sendAsyncMessage("DOMTitleChanged", { title: document.title });
michael@0 540 break;
michael@0 541
michael@0 542 case "DOMLinkAdded":
michael@0 543 let target = aEvent.originalTarget;
michael@0 544 if (!target.href || target.disabled)
michael@0 545 return;
michael@0 546
michael@0 547 let json = {
michael@0 548 windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
michael@0 549 href: target.href,
michael@0 550 charset: document.characterSet,
michael@0 551 title: target.title,
michael@0 552 rel: target.rel,
michael@0 553 type: target.type
michael@0 554 };
michael@0 555
michael@0 556 // rel=icon can also have a sizes attribute
michael@0 557 if (target.hasAttribute("sizes"))
michael@0 558 json.sizes = target.getAttribute("sizes");
michael@0 559
michael@0 560 sendAsyncMessage("DOMLinkAdded", json);
michael@0 561 break;
michael@0 562
michael@0 563 case "DOMWillOpenModalDialog":
michael@0 564 case "DOMModalDialogClosed":
michael@0 565 case "DOMWindowClose":
michael@0 566 let retvals = sendSyncMessage(aEvent.type, { });
michael@0 567 for (let i in retvals) {
michael@0 568 if (retvals[i].preventDefault) {
michael@0 569 aEvent.preventDefault();
michael@0 570 break;
michael@0 571 }
michael@0 572 }
michael@0 573 break;
michael@0 574 case "input":
michael@0 575 case "change":
michael@0 576 this._sessionEvents.add("SessionStore:collectFormdata");
michael@0 577 this._sendUpdates();
michael@0 578 break;
michael@0 579 case "scroll":
michael@0 580 this._sessionEvents.add("SessionStore:collectScrollPosition");
michael@0 581 this._sendUpdates();
michael@0 582 break;
michael@0 583 }
michael@0 584 },
michael@0 585
michael@0 586 _sendUpdates: function() {
michael@0 587 if (!this._timeout) {
michael@0 588 // Wait a little before sending the message to batch multiple changes.
michael@0 589 this._timeout = setTimeout(function() {
michael@0 590 for (let eventType of this._sessionEvents) {
michael@0 591 sendAsyncMessage(eventType, {
michael@0 592 data: this._sessionEventMap[eventType](content)
michael@0 593 });
michael@0 594 }
michael@0 595 this._sessionEvents.clear();
michael@0 596 clearTimeout(this._timeout);
michael@0 597 this._timeout = null;
michael@0 598 }.bind(this), 1000);
michael@0 599 }
michael@0 600 }
michael@0 601 };
michael@0 602
michael@0 603 DOMEvents.init();
michael@0 604
michael@0 605 let ContentScroll = {
michael@0 606 // The most recent offset set by APZC for the root scroll frame
michael@0 607 _scrollOffset: { x: 0, y: 0 },
michael@0 608
michael@0 609 init: function() {
michael@0 610 addMessageListener("Content:SetWindowSize", this);
michael@0 611
michael@0 612 addEventListener("pagehide", this, false);
michael@0 613 addEventListener("MozScrolledAreaChanged", this, false);
michael@0 614 },
michael@0 615
michael@0 616 getScrollOffset: function(aWindow) {
michael@0 617 let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
michael@0 618 let scrollX = {}, scrollY = {};
michael@0 619 cwu.getScrollXY(false, scrollX, scrollY);
michael@0 620 return { x: scrollX.value, y: scrollY.value };
michael@0 621 },
michael@0 622
michael@0 623 getScrollOffsetForElement: function(aElement) {
michael@0 624 if (aElement.parentNode == aElement.ownerDocument)
michael@0 625 return this.getScrollOffset(aElement.ownerDocument.defaultView);
michael@0 626 return { x: aElement.scrollLeft, y: aElement.scrollTop };
michael@0 627 },
michael@0 628
michael@0 629 receiveMessage: function(aMessage) {
michael@0 630 let json = aMessage.json;
michael@0 631 switch (aMessage.name) {
michael@0 632 case "Content:SetWindowSize": {
michael@0 633 let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
michael@0 634 cwu.setCSSViewport(json.width, json.height);
michael@0 635 sendAsyncMessage("Content:SetWindowSize:Complete", {});
michael@0 636 break;
michael@0 637 }
michael@0 638 }
michael@0 639 },
michael@0 640
michael@0 641 handleEvent: function(aEvent) {
michael@0 642 switch (aEvent.type) {
michael@0 643 case "pagehide":
michael@0 644 this._scrollOffset = { x: 0, y: 0 };
michael@0 645 break;
michael@0 646
michael@0 647 case "MozScrolledAreaChanged": {
michael@0 648 let doc = aEvent.originalTarget;
michael@0 649 if (content != doc.defaultView) // We are only interested in root scroll pane changes
michael@0 650 return;
michael@0 651
michael@0 652 sendAsyncMessage("MozScrolledAreaChanged", {
michael@0 653 width: aEvent.width,
michael@0 654 height: aEvent.height,
michael@0 655 left: aEvent.x + content.scrollX
michael@0 656 });
michael@0 657
michael@0 658 // Send event only after painting to make sure content views in the parent process have
michael@0 659 // been updated.
michael@0 660 addEventListener("MozAfterPaint", function afterPaint() {
michael@0 661 removeEventListener("MozAfterPaint", afterPaint, false);
michael@0 662 sendAsyncMessage("Content:UpdateDisplayPort");
michael@0 663 }, false);
michael@0 664
michael@0 665 break;
michael@0 666 }
michael@0 667 }
michael@0 668 }
michael@0 669 };
michael@0 670 this.ContentScroll = ContentScroll;
michael@0 671
michael@0 672 ContentScroll.init();
michael@0 673
michael@0 674 let ContentActive = {
michael@0 675 init: function() {
michael@0 676 addMessageListener("Content:Activate", this);
michael@0 677 addMessageListener("Content:Deactivate", this);
michael@0 678 },
michael@0 679
michael@0 680 receiveMessage: function(aMessage) {
michael@0 681 let json = aMessage.json;
michael@0 682 switch (aMessage.name) {
michael@0 683 case "Content:Deactivate":
michael@0 684 docShell.isActive = false;
michael@0 685 let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
michael@0 686 if (json.keepviewport)
michael@0 687 break;
michael@0 688 cwu.setDisplayPortForElement(0, 0, 0, 0, content.document.documentElement, 0);
michael@0 689 break;
michael@0 690
michael@0 691 case "Content:Activate":
michael@0 692 docShell.isActive = true;
michael@0 693 break;
michael@0 694 }
michael@0 695 }
michael@0 696 };
michael@0 697
michael@0 698 ContentActive.init();
michael@0 699
michael@0 700 /**
michael@0 701 * Helper class for IndexedDB, child part. Listens using
michael@0 702 * the observer service for events regarding IndexedDB
michael@0 703 * prompts, and sends messages to the parent to actually
michael@0 704 * show the prompts.
michael@0 705 */
michael@0 706 let IndexedDB = {
michael@0 707 _permissionsPrompt: "indexedDB-permissions-prompt",
michael@0 708 _permissionsResponse: "indexedDB-permissions-response",
michael@0 709
michael@0 710 _quotaPrompt: "indexedDB-quota-prompt",
michael@0 711 _quotaResponse: "indexedDB-quota-response",
michael@0 712 _quotaCancel: "indexedDB-quota-cancel",
michael@0 713
michael@0 714 waitingObservers: [],
michael@0 715
michael@0 716 init: function IndexedDBPromptHelper_init() {
michael@0 717 let os = Services.obs;
michael@0 718 os.addObserver(this, this._permissionsPrompt, false);
michael@0 719 os.addObserver(this, this._quotaPrompt, false);
michael@0 720 os.addObserver(this, this._quotaCancel, false);
michael@0 721 addMessageListener("IndexedDB:Response", this);
michael@0 722 },
michael@0 723
michael@0 724 observe: function IndexedDBPromptHelper_observe(aSubject, aTopic, aData) {
michael@0 725 if (aTopic != this._permissionsPrompt && aTopic != this._quotaPrompt && aTopic != this._quotaCancel) {
michael@0 726 throw new Error("Unexpected topic!");
michael@0 727 }
michael@0 728
michael@0 729 let requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor);
michael@0 730 let observer = requestor.getInterface(Ci.nsIObserver);
michael@0 731
michael@0 732 let contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
michael@0 733 let contentDocument = contentWindow.document;
michael@0 734
michael@0 735 if (aTopic == this._quotaCancel) {
michael@0 736 observer.observe(null, this._quotaResponse, Ci.nsIPermissionManager.UNKNOWN_ACTION);
michael@0 737 return;
michael@0 738 }
michael@0 739
michael@0 740 // Remote to parent
michael@0 741 sendAsyncMessage("IndexedDB:Prompt", {
michael@0 742 topic: aTopic,
michael@0 743 host: contentDocument.documentURIObject.asciiHost,
michael@0 744 location: contentDocument.location.toString(),
michael@0 745 data: aData,
michael@0 746 observerId: this.addWaitingObserver(observer)
michael@0 747 });
michael@0 748 },
michael@0 749
michael@0 750 receiveMessage: function(aMessage) {
michael@0 751 let payload = aMessage.json;
michael@0 752 switch (aMessage.name) {
michael@0 753 case "IndexedDB:Response":
michael@0 754 let observer = this.getAndRemoveWaitingObserver(payload.observerId);
michael@0 755 observer.observe(null, payload.responseTopic, payload.permission);
michael@0 756 }
michael@0 757 },
michael@0 758
michael@0 759 addWaitingObserver: function(aObserver) {
michael@0 760 let observerId = 0;
michael@0 761 while (observerId in this.waitingObservers)
michael@0 762 observerId++;
michael@0 763 this.waitingObservers[observerId] = aObserver;
michael@0 764 return observerId;
michael@0 765 },
michael@0 766
michael@0 767 getAndRemoveWaitingObserver: function(aObserverId) {
michael@0 768 let observer = this.waitingObservers[aObserverId];
michael@0 769 delete this.waitingObservers[aObserverId];
michael@0 770 return observer;
michael@0 771 }
michael@0 772 };
michael@0 773
michael@0 774 IndexedDB.init();
michael@0 775

mercurial