~ubuntu-branches/ubuntu/karmic/firebug/karmic

« back to all changes in this revision

Viewing changes to content/firebug/tabWatcher.js

  • Committer: Bazaar Package Importer
  • Author(s): Jared Greenwald
  • Date: 2008-02-21 17:34:24 UTC
  • Revision ID: james.westby@ubuntu.com-20080221173424-illircvfpyvnp4uo
Tags: upstream-1.1.0~b11+svn317
ImportĀ upstreamĀ versionĀ 1.1.0~b11+svn317

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* See license.txt for terms of usage */
 
2
 
 
3
FBL.ns(function() { with (FBL) {
 
4
 
 
5
// ************************************************************************************************
 
6
// Constants
 
7
 
 
8
const nsIWebNavigation = CI("nsIWebNavigation");
 
9
const nsIWebProgressListener = CI("nsIWebProgressListener");
 
10
const nsIWebProgress = CI("nsIWebProgress");
 
11
const nsISupportsWeakReference = CI("nsISupportsWeakReference");
 
12
const nsISupports = CI("nsISupports");
 
13
const nsIURI = CI("nsIURI");
 
14
 
 
15
const NOTIFY_STATE_DOCUMENT = nsIWebProgress.NOTIFY_STATE_DOCUMENT;
 
16
 
 
17
const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW;
 
18
const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT;
 
19
const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST;
 
20
 
 
21
const STATE_START = nsIWebProgressListener.STATE_START;
 
22
const STATE_STOP = nsIWebProgressListener.STATE_STOP;
 
23
const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING;
 
24
 
 
25
const STOP_ALL = nsIWebNavigation.STOP_ALL;
 
26
 
 
27
const dummyURI = "about:layout-dummy-request";
 
28
const aboutBlank = "about:blank";
 
29
 
 
30
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
31
 
 
32
const tabBrowser = $("content");
 
33
 
 
34
// ************************************************************************************************
 
35
// Globals
 
36
 
 
37
var contexts = [];
 
38
var listeners = [];
 
39
 
 
40
// ************************************************************************************************
 
41
 
 
42
top.TabWatcher =
 
43
{
 
44
    initialize: function(owner)
 
45
    {
 
46
        // Store contexts where they can be accessed externally
 
47
        this.contexts = contexts;
 
48
 
 
49
        this.owner = owner;  // Firebug object
 
50
        this.addListener(owner);
 
51
 
 
52
        if (tabBrowser)
 
53
            tabBrowser.addProgressListener(TabProgressListener, NOTIFY_STATE_DOCUMENT);
 
54
    },
 
55
 
 
56
    destroy: function()
 
57
    {
 
58
        if (tabBrowser)
 
59
        {
 
60
            tabBrowser.removeProgressListener(TabProgressListener);
 
61
 
 
62
            for (var i = 0; i < tabBrowser.browsers.length; ++i)
 
63
            {
 
64
                var browser = tabBrowser.browsers[i];
 
65
                this.unwatchTopWindow(browser.contentWindow);
 
66
            }
 
67
        }
 
68
 
 
69
        this.removeListener(this.owner);
 
70
        this.owner = null;
 
71
    },
 
72
 
 
73
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
74
 
 
75
    activate: function()
 
76
    {
 
77
        if (tabBrowser)
 
78
            this.watchBrowser(tabBrowser.selectedBrowser);
 
79
    },
 
80
 
 
81
    deactivate: function()
 
82
    {
 
83
        if (tabBrowser)
 
84
        {
 
85
            var currentSelected = false;
 
86
            for (var i = 0; i < tabBrowser.browsers.length; ++i)
 
87
            {
 
88
                var browser = tabBrowser.browsers[i];
 
89
                if (!this.owner.isURIAllowed(safeGetURI(browser)))
 
90
                {
 
91
                    this.unwatchTopWindow(browser.contentWindow);
 
92
 
 
93
                    if (browser == tabBrowser.selectedBrowser)
 
94
                        currentSelected = true;
 
95
                }
 
96
            }
 
97
            return currentSelected;
 
98
        }
 
99
    },
 
100
 
 
101
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
102
 
 
103
    /**
 
104
     * Attaches to a top-level window. Creates context unless we just re-activated on an existing context
 
105
     */
 
106
    watchTopWindow: function(win, uri)
 
107
    {
 
108
        if (tabBrowser.selectedBrowser.cancelNextLoad)
 
109
        {
 
110
            // We need to cancel this load and try again after a delay... this is used
 
111
            // mainly to prevent chaos while when the debugger is active when a page
 
112
            // is unloaded
 
113
            delete tabBrowser.selectedBrowser.cancelNextLoad;
 
114
            tabBrowser.selectedBrowser.webNavigation.stop(STOP_ALL);
 
115
            delayBrowserLoad(tabBrowser.selectedBrowser, win.location.href);
 
116
            return;
 
117
        }
 
118
 
 
119
        var context = this.getContextByWindow(win);
 
120
        if (!context)
 
121
        {
 
122
            if (!this.owner.enableContext(win,uri))
 
123
            {
 
124
                return this.watchContext(win, null);
 
125
            }
 
126
        }
 
127
 
 
128
        if (!context)
 
129
        {
 
130
            var browser = this.getBrowserByWindow(win);
 
131
            if (!fbs.countContext(true))
 
132
                return;
 
133
 
 
134
            // If the page is reloaded, store the persisted state from the previous
 
135
            // page on the new context
 
136
            var persistedState = browser.persistedState;
 
137
            delete browser.persistedState;
 
138
            if (!persistedState || persistedState.location != win.location.href)
 
139
                persistedState = null;
 
140
 
 
141
            context = this.owner.createTabContext(win, browser, browser.chrome, persistedState);
 
142
            contexts.push(context);
 
143
 
 
144
            this.dispatch("initContext", [context]);
 
145
 
 
146
            win.addEventListener("pagehide", onUnloadTopWindow, true);
 
147
            win.addEventListener("pageshow", onLoadWindowContent, true);
 
148
            win.addEventListener("DOMContentLoaded", onLoadWindowContent, true);
 
149
        }
 
150
        // XXXjjb at this point we either have context or we just pushed null into contexts and sent it to init...
 
151
 
 
152
        // This is one of two places that loaded is set. The other is in watchLoadedTopWindow
 
153
        if (context)
 
154
            context.loaded = !context.browser.webProgress.isLoadingDocument;
 
155
 
 
156
        this.watchContext(win, context);
 
157
    },
 
158
 
 
159
    /**
 
160
     * Called once the document within a tab is completely loaded.
 
161
     */
 
162
    watchLoadedTopWindow: function(win)
 
163
    {
 
164
        var isSystem = isSystemPage(win);
 
165
 
 
166
        var context = this.getContextByWindow(win);
 
167
        if ((context && !context.window) || (isSystem && !Firebug.allowSystemPages))
 
168
        {
 
169
            this.unwatchTopWindow(win);
 
170
            this.watchContext(win, null, isSystem);
 
171
            return;
 
172
        }
 
173
 
 
174
        if (context && !context.loaded)
 
175
        {
 
176
            context.loaded = true;
 
177
            this.dispatch("loadedContext", [context]);
 
178
        }
 
179
    },
 
180
 
 
181
    /**
 
182
     * Attaches to a window that may be either top-level or a frame within the page.
 
183
     */
 
184
    watchWindow: function(win, context)
 
185
    {
 
186
        if (!context)
 
187
            context = this.getContextByWindow(getRootWindow(win));
 
188
 
 
189
        // Unfortunately, dummy requests that trigger the call to watchWindow
 
190
        // are called several times, so we have to avoid dispatching watchWindow
 
191
        // more than once
 
192
        var href = win.location.href;
 
193
        if (context && context.windows.indexOf(win) == -1 && href != aboutBlank)
 
194
        {
 
195
            context.windows.push(win);
 
196
 
 
197
            var eventType = (win.parent == win) ? "pagehide" : "unload";
 
198
            win.addEventListener(eventType, onUnloadWindow, false);
 
199
            this.dispatch("watchWindow", [context, win]);
 
200
        }
 
201
    },
 
202
 
 
203
    /**
 
204
     * Detaches from a top-level window. Destroys context
 
205
     */
 
206
    unwatchTopWindow: function(win)
 
207
    {
 
208
        var context = this.getContextByWindow(win);
 
209
        this.unwatchContext(win, context);
 
210
    },
 
211
 
 
212
    /**
 
213
     * Detaches from a window, top-level or not.
 
214
     */
 
215
    unwatchWindow: function(win)
 
216
    {
 
217
        var context = this.getContextByWindow(win);
 
218
 
 
219
        var index = context ? context.windows.indexOf(win) : -1;
 
220
        if (index != -1)
 
221
        {
 
222
            context.windows.splice(index, 1);
 
223
            this.dispatch("unwatchWindow", [context, win]);  // XXXjjb Joe check
 
224
        }
 
225
    },
 
226
 
 
227
    /**
 
228
     * Attaches to the window inside a browser because of user-activation
 
229
     */
 
230
    watchBrowser: function(browser)
 
231
    {
 
232
        this.watchTopWindow(browser.contentWindow, safeGetURI(browser));
 
233
    },
 
234
 
 
235
    unwatchBrowser: function(browser)
 
236
    {
 
237
        this.unwatchTopWindow(browser.contentWindow);
 
238
    },
 
239
 
 
240
    watchContext: function(win, context, isSystem)
 
241
    {
 
242
        var browser = context ? context.browser : this.getBrowserByWindow(win);
 
243
        if (browser)
 
244
            browser.isSystemPage = isSystem;
 
245
 
 
246
        this.dispatch("showContext", [browser, context]);
 
247
    },
 
248
 
 
249
    unwatchContext: function(win, context)
 
250
    {
 
251
        if (!context)
 
252
        {
 
253
            var browser = this.getBrowserByWindow(win);
 
254
            this.owner.destroyTabContext(browser, null);
 
255
            return;
 
256
        }
 
257
 
 
258
        var persistedState = {location: context.window.location.href};
 
259
        context.browser.persistedState = persistedState;
 
260
 
 
261
        iterateWindows(context.window, function(win)
 
262
        {
 
263
            TabWatcher.dispatch("unwatchWindow", [context, win]);
 
264
        });
 
265
 
 
266
        this.dispatch("destroyContext", [context, persistedState]);
 
267
 
 
268
        if (this.cancelNextLoad)
 
269
        {
 
270
            delete this.cancelNextLoad;
 
271
            context.browser.cancelNextLoad = true;
 
272
        }
 
273
 
 
274
        try
 
275
        {
 
276
            context.window.removeEventListener("pagehide", onUnloadTopWindow, true);
 
277
        }
 
278
        catch (exc)
 
279
        {
 
280
        }
 
281
 
 
282
        fbs.countContext(false);
 
283
 
 
284
        this.owner.destroyTabContext(context.browser, context);
 
285
        context.destroy(persistedState);
 
286
 
 
287
        remove(contexts, context);
 
288
        for (var p in context)
 
289
            delete context[p];
 
290
    },
 
291
 
 
292
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
293
 
 
294
    getContextByWindow: function(win)
 
295
    {
 
296
        while (win && win.parent != win)
 
297
            win = win.parent;
 
298
 
 
299
        if (!win) // eg search bar, and sometimes win.parent is null??
 
300
            return;
 
301
 
 
302
        for (var i = 0; i < contexts.length; ++i)
 
303
        {
 
304
            var context = contexts[i];
 
305
            if (context.window == win)
 
306
                return context;
 
307
        }
 
308
    },
 
309
 
 
310
    getBrowserByWindow: function(win)
 
311
    {
 
312
        for (var i = 0; i < tabBrowser.browsers.length; ++i)
 
313
        {
 
314
            var browser = tabBrowser.browsers[i];
 
315
            if (browser.contentWindow == win)
 
316
            {
 
317
                if (!browser.chrome)
 
318
                {
 
319
                    browser.chrome = FirebugChrome;
 
320
                    browser.addProgressListener(FrameProgressListener, NOTIFY_STATE_DOCUMENT);
 
321
                }
 
322
                return browser;
 
323
            }
 
324
        }
 
325
 
 
326
        return null;
 
327
    },
 
328
 
 
329
    iterateContexts: function(fn)
 
330
    {
 
331
        for (var i = 0; i < contexts.length; ++i)
 
332
            fn(contexts[i]);
 
333
    },
 
334
 
 
335
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 
336
 
 
337
    addListener: function(listener)
 
338
    {
 
339
        listeners.push(listener);
 
340
    },
 
341
 
 
342
    removeListener: function(listener)
 
343
    {
 
344
        remove(listeners, listener);
 
345
    },
 
346
 
 
347
    dispatch: function(name, args)
 
348
    {
 
349
        for (var i = 0; i < listeners.length; ++i)
 
350
        {
 
351
            var listener = listeners[i];
 
352
            if (name in listener)
 
353
            {
 
354
                try
 
355
                {
 
356
                    listener[name].apply(listener, args);
 
357
                }
 
358
                catch (exc)
 
359
                {
 
360
                    ERROR(exc);
 
361
                }
 
362
            }
 
363
        }
 
364
    }
 
365
};
 
366
 
 
367
// ************************************************************************************************
 
368
 
 
369
var BaseProgressListener =
 
370
{
 
371
    QueryInterface : function(iid)
 
372
    {
 
373
        if (iid.equals(nsIWebProgressListener) ||
 
374
            iid.equals(nsISupportsWeakReference) ||
 
375
            iid.equals(nsISupports))
 
376
        {
 
377
            return this;
 
378
        }
 
379
 
 
380
        throw Components.results.NS_NOINTERFACE;
 
381
    },
 
382
 
 
383
    stateIsRequest: false,
 
384
    onLocationChange: function() {},
 
385
    onStateChange : function() {},
 
386
    onProgressChange : function() {},
 
387
    onStatusChange : function() {},
 
388
    onSecurityChange : function() {},
 
389
    onLinkIconAvailable : function() {}
 
390
};
 
391
 
 
392
// ************************************************************************************************
 
393
 
 
394
var TabProgressListener = extend(BaseProgressListener,
 
395
{
 
396
    onLocationChange: function(progress, request, uri)
 
397
    {
 
398
        // Only watch windows that are their own parent - e.g. not frames
 
399
        if (progress.DOMWindow.parent == progress.DOMWindow)
 
400
        {
 
401
            TabWatcher.watchTopWindow(progress.DOMWindow, uri);
 
402
        }
 
403
    },
 
404
 
 
405
    onStateChange: function(progress, request, flag, status)
 
406
    {
 
407
        /*if (flag & STATE_STOP)
 
408
        {
 
409
            var win = progress.DOMWindow;
 
410
            if (win && win.parent == win)
 
411
                TabWatcher.watchLoadedTopWindow(progress.DOMWindow);
 
412
        }*/
 
413
    }
 
414
});
 
415
 
 
416
// ************************************************************************************************
 
417
 
 
418
var FrameProgressListener = extend(BaseProgressListener,
 
419
{
 
420
    onStateChange: function(progress, request, flag, status)
 
421
    {
 
422
        if (flag & STATE_IS_REQUEST && flag & STATE_START)
 
423
        {
 
424
            // We need to get the hook in as soon as the new DOMWindow is created, but before
 
425
            // it starts executing any scripts in the page.  After lengthy analysis, it seems
 
426
            // that the start of these "dummy" requests is the only state that works.
 
427
 
 
428
            var safeName = safeGetName(request);
 
429
            if (safeName && ((safeName == dummyURI) || safeName == "about:document-onload-blocker") )
 
430
            {
 
431
                var win = progress.DOMWindow;
 
432
                // Another weird edge case here - when opening a new tab with about:blank,
 
433
                // "unload" is dispatched to the document, but onLocationChange is not called
 
434
                // again, so we have to call watchTopWindow here
 
435
                //if (win.parent == win && win.location.href == "about:blank")
 
436
                //    TabWatcher.watchTopWindow(win, win.location);
 
437
                // XXXms check this
 
438
                if (win.parent == win && (win.location.href == "about:blank" ))//  || safeName == "about:document-onload-blocker"))
 
439
                {
 
440
                    TabWatcher.watchTopWindow(win, win.location.href);
 
441
                    return;  // new one under our thumb
 
442
                }
 
443
            }
 
444
        }
 
445
 
 
446
        // Later I discovered that XHTML documents don't dispatch the dummy requests, so this
 
447
        // is our best shot here at hooking them.
 
448
        if (flag & STATE_IS_DOCUMENT && flag & STATE_TRANSFERRING)
 
449
        {
 
450
            TabWatcher.watchWindow(progress.DOMWindow);
 
451
            return;
 
452
        }
 
453
 
 
454
    }
 
455
});
 
456
 
 
457
// ************************************************************************************************
 
458
// Local Helpers
 
459
 
 
460
 
 
461
function onUnloadTopWindow(event)
 
462
{
 
463
    TabWatcher.unwatchTopWindow(event.currentTarget);
 
464
}
 
465
 
 
466
function onLoadWindowContent(event)
 
467
{
 
468
    var win = event.currentTarget;
 
469
    try
 
470
    {
 
471
        win.removeEventListener("pageshow", onLoadWindowContent, true);
 
472
    }
 
473
    catch (exc) {}
 
474
 
 
475
    try
 
476
    {
 
477
        win.removeEventListener("DOMContentLoaded", onLoadWindowContent, true);
 
478
    }
 
479
    catch (exc) {}
 
480
 
 
481
    // Signal that we got the onLoadWindowContent event. This prevents the FrameProgressListener from sending it.
 
482
    var context = TabWatcher.getContextByWindow(win);
 
483
    if (context)
 
484
        context.onLoadWindowContent = true;
 
485
 
 
486
    // Calling this after a timeout because I'm finding some cases where calling
 
487
    // it here causes freezeup when this results in loading a script file. This fixes that.
 
488
    setTimeout(function()
 
489
    {
 
490
        try
 
491
        {
 
492
            TabWatcher.watchLoadedTopWindow(win);
 
493
        }
 
494
        catch(exc)
 
495
        {
 
496
            ERROR(exc);
 
497
        }
 
498
 
 
499
    });
 
500
}
 
501
 
 
502
function onUnloadWindow(event)
 
503
{
 
504
    var win = event.currentTarget;
 
505
    var eventType = (win.parent == win) ? "pagehide" : "unload";
 
506
    win.removeEventListener(eventType, onUnloadWindow, false);
 
507
    TabWatcher.unwatchWindow(win);
 
508
}
 
509
 
 
510
function delayBrowserLoad(browser, uri)
 
511
{
 
512
    setTimeout(function() { browser.loadURI(uri); }, 100);
 
513
}
 
514
 
 
515
function safeGetName(request)
 
516
{
 
517
    try
 
518
    {
 
519
        return request.name;
 
520
    }
 
521
    catch (exc)
 
522
    {
 
523
        return null;
 
524
    }
 
525
}
 
526
 
 
527
function safeGetURI(browser)
 
528
{
 
529
    try
 
530
    {
 
531
        return browser.currentURI;
 
532
    }
 
533
    catch (exc)
 
534
    {
 
535
        return null;
 
536
    }
 
537
}
 
538
 
 
539
function getStateDescription(flag) {
 
540
    var state = "";
 
541
    if (flag & nsIWebProgressListener.STATE_START) state += "STATE_START ";
 
542
    else if (flag & nsIWebProgressListener.STATE_REDIRECTING) state += "STATE_REDIRECTING ";
 
543
    else if (flag & nsIWebProgressListener.STATE_TRANSFERRING) state += "STATE_TRANSFERRING ";
 
544
    else if (flag & nsIWebProgressListener.STATE_NEGOTIATING) state += "STATE_NEGOTIATING ";
 
545
    else if (flag & nsIWebProgressListener.STATE_STOP) state += "STATE_STOP ";
 
546
 
 
547
    if (flag & nsIWebProgressListener.STATE_IS_REQUEST) state += "STATE_IS_REQUEST ";
 
548
    if (flag & nsIWebProgressListener.STATE_IS_DOCUMENT) state += "STATE_IS_DOCUMENT ";
 
549
    if (flag & nsIWebProgressListener.STATE_IS_NETWORK) state += "STATE_IS_NETWORK ";
 
550
    if (flag & nsIWebProgressListener.STATE_IS_WINDOW) state += "STATE_IS_WINDOW ";
 
551
    if (flag & nsIWebProgressListener.STATE_RESTORING) state += "STATE_RESTORING ";
 
552
    if (flag & nsIWebProgressListener.STATE_IS_INSECURE) state += "STATE_IS_INSECURE ";
 
553
    if (flag & nsIWebProgressListener.STATE_IS_BROKEN) state += "STATE_IS_BROKEN ";
 
554
    if (flag & nsIWebProgressListener.STATE_IS_SECURE) state += "STATE_IS_SECURE ";
 
555
    if (flag & nsIWebProgressListener.STATE_SECURE_HIGH) state += "STATE_SECURE_HIGH ";
 
556
    if (flag & nsIWebProgressListener.STATE_SECURE_MED) state += "STATE_SECURE_MED ";
 
557
    if (flag & nsIWebProgressListener.STATE_SECURE_LOW) state += "STATE_SECURE_LOW ";
 
558
 
 
559
    return state;
 
560
}
 
561
 
 
562
// ************************************************************************************************
 
563
 
 
564
}});