/* * Copyright 2003 Phil Mellor * Copyright 2006 James Bursa * Copyright 2004 Andrew Timmins * Copyright 2004 John Tytgat * Copyright 2006 Richard Wilson * Copyright 2008 Michael Drake * Copyright 2009 Paul Blokus * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** \file * Browser window creation and manipulation (implementation). */ #include #include #include #include #include #include #include #include #include "curl/curl.h" #include "utils/config.h" #include "content/content.h" #include "content/fetch.h" #include "content/hlcache.h" #include "content/urldb.h" #include "desktop/401login.h" #include "desktop/browser.h" #include "desktop/download.h" #include "desktop/frames.h" #include "desktop/history_core.h" #include "desktop/hotlist.h" #include "desktop/gui.h" #include "desktop/knockout.h" #include "desktop/options.h" #include "desktop/selection.h" #include "desktop/textinput.h" #include "desktop/plotters.h" #include "render/form.h" #include "render/html.h" #include "render/textplain.h" #include "utils/log.h" #include "utils/messages.h" #include "utils/schedule.h" #include "utils/url.h" #include "utils/utils.h" #include "utils/utf8.h" /** browser window which is being redrawn. Valid only during redraw. */ struct browser_window *current_redraw_browser; /** one or more windows require a reformat */ bool browser_reformat_pending; /** maximum frame depth */ #define FRAME_DEPTH 8 static nserror browser_window_callback(hlcache_handle *c, const hlcache_event *event, void *pw); static void browser_window_refresh(void *p); static bool browser_window_check_throbber(struct browser_window *bw); static void browser_window_convert_to_download(struct browser_window *bw, llcache_handle *stream); static void browser_window_start_throbber(struct browser_window *bw); static void browser_window_stop_throbber(struct browser_window *bw); static void browser_window_set_icon(struct browser_window *bw); static void browser_window_destroy_children(struct browser_window *bw); static void browser_window_destroy_internal(struct browser_window *bw); static void browser_window_set_scale_internal(struct browser_window *bw, float scale); static void browser_window_find_target_internal(struct browser_window *bw, const char *target, int depth, struct browser_window *page, int *rdepth, struct browser_window **bw_target); /* exported interface, documented in browser.h */ bool browser_window_redraw(struct browser_window *bw, int x, int y, const struct rect *clip) { int width = 0; int height = 0; bool plot_ok = true; content_type content_type; if (bw == NULL) { LOG(("NULL browser window")); return false; } if (bw->current_content == NULL) { /* Browser window has no content, render blank fill */ plot.clip(clip); return plot.rectangle(clip->x0, clip->y0, clip->x1, clip->y1, plot_style_fill_white); } /* Browser window has content */ if (bw->browser_window_type != BROWSER_WINDOW_IFRAME && plot.option_knockout) knockout_plot_start(&plot); plot.clip(clip); content_type = content_get_type(bw->current_content); if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) { /* Set render area according to scale */ width = content_get_width(bw->current_content) * bw->scale; height = content_get_height(bw->current_content) * bw->scale; /* Non-HTML may not fill viewport to extents, so plot white * background fill */ plot_ok &= plot.rectangle(clip->x0, clip->y0, clip->x1, clip->y1, plot_style_fill_white); } /* Render the content */ plot_ok &= content_redraw(bw->current_content, x, y, width, height, clip, bw->scale, 0xFFFFFF, false, false); if (bw->browser_window_type != BROWSER_WINDOW_IFRAME && plot.option_knockout) knockout_plot_end(); return plot_ok; } /* exported interface, documented in browser.h */ bool browser_window_redraw_ready(struct browser_window *bw) { if (bw == NULL) { LOG(("NULL browser window")); return false; } else if (bw->current_content != NULL) { /* Can't render locked contents */ return !content_is_locked(bw->current_content); } return true; } /* exported interface, documented in browser.h */ void browser_window_update_extent(struct browser_window *bw) { switch (bw->browser_window_type) { default: /* Fall through until core frame(set)s are implemented */ case BROWSER_WINDOW_NORMAL: gui_window_update_extent(bw->window); break; case BROWSER_WINDOW_IFRAME: /* TODO */ break; } } /* exported interface, documented in browser.h */ void browser_window_get_position(struct browser_window *bw, bool root, int *pos_x, int *pos_y) { int x, y; *pos_x = 0; *pos_y = 0; assert(bw != NULL); while (bw) { switch (bw->browser_window_type) { default: /* fall through to NORMAL until frame(set)s are handled * in the core */ case BROWSER_WINDOW_NORMAL: /* There is no offset to the root browser window */ break; case BROWSER_WINDOW_IFRAME: /* offset comes from its box position in parent bw */ box_coords(bw->box, &x, &y); *pos_x += x; *pos_y += y; break; } bw = bw->parent; if (!root) { /* return if we just wanted the position in the parent * browser window. */ return; } } } /** * Create and open a new root browser window with the given page. * * \param url URL to start fetching in the new window (copied) * \param clone The browser window to clone * \param referer The referring uri (copied), or 0 if none */ struct browser_window *browser_window_create(const char *url, struct browser_window *clone, const char *referer, bool history_add, bool new_tab) { struct browser_window *bw; struct browser_window *top; assert(clone || history_add); if ((bw = calloc(1, sizeof *bw)) == NULL) { warn_user("NoMemory", 0); return NULL; } /* Initialise common parts */ browser_window_initialise_common(bw, clone); /* window characteristics */ bw->browser_window_type = BROWSER_WINDOW_NORMAL; bw->scrolling = SCROLLING_YES; bw->border = true; bw->no_resize = true; bw->last_action = wallclock(); bw->sel = selection_create(); /* gui window */ /* from the front end's pov, it clones the top level browser window, * so find that. */ top = clone; while (top && !top->window && top->parent) { top = top->parent; } bw->window = gui_create_browser_window(bw, top, new_tab); if (bw->window == NULL) { browser_window_destroy(bw); return NULL; } if (url) browser_window_go(bw, url, referer, history_add); return bw; } /** * Initialise common parts of a browser window * * \param bw The window to initialise * \param clone The window to clone, or NULL if none */ void browser_window_initialise_common(struct browser_window *bw, struct browser_window *clone) { assert(bw); if (!clone) bw->history = history_create(); else bw->history = history_clone(clone->history); /* window characteristics */ bw->sel = NULL; bw->refresh_interval = -1; bw->reformat_pending = false; bw->drag_type = DRAGGING_NONE; bw->scale = (float) option_scale / 100.0; bw->focus = NULL; /* initialise status text cache */ bw->status_text = NULL; bw->status_text_len = 0; bw->status_match = 0; bw->status_miss = 0; } /** * Start fetching a page in a browser window. * * \param bw browser window * \param url URL to start fetching (copied) * \param referer the referring uri (copied), or 0 if none * * Any existing fetches in the window are aborted. */ void browser_window_go(struct browser_window *bw, const char *url, const char *referer, bool history_add) { /* All fetches passing through here are verifiable * (i.e are the result of user action) */ browser_window_go_post(bw, url, 0, 0, history_add, referer, false, true, NULL); } /** * Start a download of the given URL from a browser window. * * \param bw browser window * \param url URL to start downloading (copied) * \param referer the referring uri (copied), or 0 if none */ void browser_window_download(struct browser_window *bw, const char *url, const char *referer) { browser_window_go_post(bw, url, 0, 0, false, referer, true, true, NULL); } /** * Start fetching a page in a browser window. * * \param bw browser window * \param url URL to start fetching (copied) * \param referer the referring uri (copied), or 0 if none * * Any existing fetches in the window are aborted. */ void browser_window_go_unverifiable(struct browser_window *bw, const char *url, const char *referer, bool history_add, hlcache_handle *parent) { /* All fetches passing through here are unverifiable * (i.e are not the result of user action) */ browser_window_go_post(bw, url, 0, 0, history_add, referer, false, false, parent); } /** * Start fetching a page in a browser window, POSTing form data. * * \param bw browser window * \param url URL to start fetching (copied) * \param post_urlenc url encoded post data, or 0 if none * \param post_multipart multipart post data, or 0 if none * \param add_to_history add to window history * \param referer the referring uri (copied), or 0 if none * \param download download, rather than render the uri * \param verifiable this transaction is verifiable * \param parent Parent content, or NULL * * Any existing fetches in the window are aborted. * * If post_urlenc and post_multipart are 0 the url is fetched using GET. * * The page is not added to the window history if add_to_history is false. * This should be used when returning to a page in the window history. */ void browser_window_go_post(struct browser_window *bw, const char *url, char *post_urlenc, struct fetch_multipart_data *post_multipart, bool add_to_history, const char *referer, bool download, bool verifiable, hlcache_handle *parent) { hlcache_handle *c; char *url2; char *fragment; url_func_result res; int depth = 0; struct browser_window *cur; uint32_t fetch_flags = 0; bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL); llcache_post_data post; hlcache_child_context child; nserror error; LOG(("bw %p, url %s", bw, url)); assert(bw); assert(url); /* don't allow massively nested framesets */ for (cur = bw; cur->parent; cur = cur->parent) depth++; if (depth > FRAME_DEPTH) { LOG(("frame depth too high.")); return; } /* Set up retrieval parameters */ if (verifiable) fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; if (post_multipart != NULL) { post.type = LLCACHE_POST_MULTIPART; post.data.multipart = post_multipart; } else if (post_urlenc != NULL) { post.type = LLCACHE_POST_URL_ENCODED; post.data.urlenc = post_urlenc; } if (parent != NULL && content_get_type(parent) == CONTENT_HTML) { child.charset = html_get_encoding(parent); child.quirks = content_get_quirks(parent); } /* Normalize the request URL */ res = url_normalize(url, &url2); if (res != URL_FUNC_OK) { LOG(("failed to normalize url %s", url)); return; } /* Get download out of the way */ if (download) { llcache_handle *l; fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH; fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA; error = llcache_handle_retrieve(url2, fetch_flags, referer, fetch_is_post ? &post : NULL, NULL, NULL, &l); if (error == NSERROR_NO_FETCH_HANDLER) { gui_launch_url(url2); } else if (error != NSERROR_OK) { LOG(("Failed to fetch download: %d", error)); } else { error = download_context_create(l, bw->window); if (error != NSERROR_OK) { LOG(("Failed creating download context: %d", error)); llcache_handle_abort(l); llcache_handle_release(l); } } free(url2); return; } free(bw->frag_id); bw->frag_id = NULL; /* find any fragment identifier on end of URL */ res = url_fragment(url2, &fragment); if (res == URL_FUNC_NOMEM) { free(url2); warn_user("NoMemory", 0); return; } else if (res == URL_FUNC_OK) { bool same_url = false; bw->frag_id = fragment; /* Compare new URL with existing one (ignoring fragments) */ if (bw->current_content != NULL && content_get_url(bw->current_content) != NULL) { res = url_compare(content_get_url(bw->current_content), url2, true, &same_url); if (res == URL_FUNC_NOMEM) { free(url2); warn_user("NoMemory", 0); return; } else if (res == URL_FUNC_FAILED) { same_url = false; } } /* if we're simply moving to another ID on the same page, * don't bother to fetch, just update the window. */ if (same_url && fetch_is_post == false && strchr(url2, '?') == 0) { free(url2); if (add_to_history) history_add(bw->history, bw->current_content, bw->frag_id); browser_window_update(bw, false); if (bw->current_content != NULL) { browser_window_refresh_url_bar(bw, content_get_url(bw->current_content), bw->frag_id); } return; } } browser_window_stop(bw); browser_window_remove_caret(bw); browser_window_destroy_children(bw); LOG(("Loading '%s'", url2)); browser_window_set_status(bw, messages_get("Loading")); bw->history_add = add_to_history; error = hlcache_handle_retrieve(url2, fetch_flags | HLCACHE_RETRIEVE_MAY_DOWNLOAD, referer, fetch_is_post ? &post : NULL, browser_window_callback, bw, parent != NULL ? &child : NULL, CONTENT_ANY, &c); if (error == NSERROR_NO_FETCH_HANDLER) { gui_launch_url(url2); free(url2); return; } else if (error != NSERROR_OK) { free(url2); browser_window_set_status(bw, messages_get("NoMemory")); warn_user("NoMemory", 0); return; } free(url2); bw->loading_content = c; browser_window_start_throbber(bw); browser_window_refresh_url_bar(bw, url, NULL); } /** * Callback for fetchcache() for browser window fetches. */ nserror browser_window_callback(hlcache_handle *c, const hlcache_event *event, void *pw) { struct browser_window *bw = pw; switch (event->type) { case CONTENT_MSG_DOWNLOAD: assert(bw->loading_content == c); browser_window_convert_to_download(bw, event->data.download); if (bw->current_content != NULL) { browser_window_refresh_url_bar(bw, content_get_url(bw->current_content), bw->frag_id); } break; case CONTENT_MSG_LOADING: assert(bw->loading_content == c); #ifdef WITH_THEME_INSTALL if (content_get_type(c) == CONTENT_THEME) { theme_install_start(c); bw->loading_content = NULL; browser_window_stop_throbber(bw); } else #endif { bw->refresh_interval = -1; browser_window_set_status(bw, content_get_status_message(c)); } break; case CONTENT_MSG_READY: { int width, height; assert(bw->loading_content == c); if (bw->current_content != NULL) { content_status status = content_get_status(bw->current_content); if (status == CONTENT_STATUS_READY || status == CONTENT_STATUS_DONE) content_close(bw->current_content); hlcache_handle_release(bw->current_content); } bw->current_content = c; bw->loading_content = NULL; /* Format the new content to the correct dimensions */ browser_window_get_dimensions(bw, &width, &height, true); content_reformat(c, width, height); browser_window_remove_caret(bw); bw->scrollbar = NULL; if (bw->window) gui_window_new_content(bw->window); browser_window_refresh_url_bar(bw, content_get_url(bw->current_content), bw->frag_id); /* new content; set scroll_to_top */ browser_window_update(bw, true); content_open(c, bw, 0, 0, 0); browser_window_set_status(bw, content_get_status_message(c)); /* history */ if (bw->history_add && bw->history) { const char *url = content_get_url(c); history_add(bw->history, c, bw->frag_id); if (urldb_add_url(url)) { urldb_set_url_title(url, content_get_title(c)); urldb_update_url_visit_data(url); urldb_set_url_content_type(url, content_get_type(c)); /* This is safe as we've just added the URL */ global_history_add(urldb_get_url(url)); } } /* favicon preload */ if (bw->browser_window_type == BROWSER_WINDOW_NORMAL && content_get_type(c) == CONTENT_HTML) { gui_window_set_icon(bw->window, html_get_favicon(bw->current_content)); } /* text selection */ if (content_get_type(c) == CONTENT_HTML) selection_init(bw->sel, html_get_box_tree(bw->current_content)); if (content_get_type(c) == CONTENT_TEXTPLAIN) selection_init(bw->sel, NULL); /* frames */ if (content_get_type(c) == CONTENT_HTML && html_get_frameset(c) != NULL) browser_window_create_frameset(bw, html_get_frameset(c)); if (content_get_type(c) == CONTENT_HTML && html_get_iframe(c) != NULL) browser_window_create_iframes(bw, html_get_iframe(c)); } break; case CONTENT_MSG_DONE: assert(bw->current_content == c); browser_window_update(bw, false); browser_window_set_status(bw, content_get_status_message(c)); browser_window_stop_throbber(bw); browser_window_set_icon(bw); history_update(bw->history, c); hotlist_visited(c); if (bw->refresh_interval != -1) schedule(bw->refresh_interval, browser_window_refresh, bw); break; case CONTENT_MSG_ERROR: browser_window_set_status(bw, event->data.error); /* Only warn the user about errors in top-level windows */ if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) warn_user(event->data.error, 0); if (c == bw->loading_content) bw->loading_content = NULL; else if (c == bw->current_content) { bw->current_content = NULL; browser_window_remove_caret(bw); bw->scrollbar = NULL; selection_init(bw->sel, NULL); } hlcache_handle_release(c); browser_window_stop_throbber(bw); break; case CONTENT_MSG_STATUS: browser_window_set_status(bw, content_get_status_message(c)); break; case CONTENT_MSG_REFORMAT: if (c == bw->current_content && content_get_type(c) == CONTENT_HTML) { /* reposition frames */ if (html_get_frameset(c) != NULL) browser_window_recalculate_frameset(bw); /* reflow iframe positions */ if (html_get_iframe(c) != NULL) browser_window_recalculate_iframes(bw); /* box tree may have changed, need to relabel */ selection_reinit(bw->sel, html_get_box_tree(c)); } if (bw->move_callback) bw->move_callback(bw, bw->caret_p); browser_window_update(bw, false); break; case CONTENT_MSG_REDRAW: browser_window_update_box(bw, &event->data); break; case CONTENT_MSG_REFRESH: bw->refresh_interval = event->data.delay * 100; break; case CONTENT_MSG_FAVICON_REFRESH: /* Cause the GUI to update */ if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { gui_window_set_icon(bw->window, html_get_favicon(bw->current_content)); } break; default: assert(0); } return NSERROR_OK; } /* * Get the dimensions of the area a browser window occupies * * \param bw The browser window to get dimensions of * \param width Updated to the browser window viewport width * \param height Updated to the browser window viewport height * \param scaled Whether we want the height with scale applied */ void browser_window_get_dimensions(struct browser_window *bw, int *width, int *height, bool scaled) { struct rect rect; switch (bw->browser_window_type) { case BROWSER_WINDOW_IFRAME: /* browser window is size of associated box */ box_bounds(bw->box, &rect); /* TODO: Handle scale */ *width = rect.x1 - rect.x0; *height = rect.y1 - rect.y0; break; case BROWSER_WINDOW_FRAME: case BROWSER_WINDOW_FRAMESET: case BROWSER_WINDOW_NORMAL: /* root window (or frame(set), currently); browser window is * size of gui window viewport */ assert(bw->window); gui_window_get_dimensions(bw->window, width, height, scaled); break; } } /** * Transfer the loading_content to a new download window. */ void browser_window_convert_to_download(struct browser_window *bw, llcache_handle *stream) { nserror error; error = download_context_create(stream, bw->window); if (error != NSERROR_OK) { llcache_handle_abort(stream); llcache_handle_release(stream); return; } /* remove content from browser window */ hlcache_handle_release(bw->loading_content); bw->loading_content = NULL; browser_window_stop_throbber(bw); } /** * Handle meta http-equiv refresh time elapsing by loading a new page. * * \param p browser window to refresh with new page */ void browser_window_refresh(void *p) { struct browser_window *bw = p; bool history_add = true; const char *url; const char *refresh; assert(bw->current_content != NULL && (content_get_status(bw->current_content) == CONTENT_STATUS_READY || content_get_status(bw->current_content) == CONTENT_STATUS_DONE)); /* Ignore if the refresh URL has gone * (may happen if a fetch error occurred) */ refresh = content_get_refresh_url(bw->current_content); if (refresh == NULL) return; /* mark this content as invalid so it gets flushed from the cache */ content_invalidate_reuse_data(bw->current_content); url = content_get_url(bw->current_content); if (url != NULL && strcmp(url, refresh) == 0) history_add = false; /* Treat an (almost) immediate refresh in a top-level browser window as * if it were an HTTP redirect, and thus make the resulting fetch * verifiable. * * See fetchcache.c for why redirected fetches should be verifiable at * all. */ if (bw->refresh_interval <= 100 && bw->parent == NULL) { browser_window_go(bw, refresh, url, history_add); } else { browser_window_go_unverifiable(bw, refresh, url, history_add, bw->current_content); } } /** * Start the busy indicator. * * \param bw browser window */ void browser_window_start_throbber(struct browser_window *bw) { bw->throbbing = true; while (bw->parent) bw = bw->parent; gui_window_start_throbber(bw->window); } /** * Stop the busy indicator. * * \param bw browser window */ void browser_window_stop_throbber(struct browser_window *bw) { bw->throbbing = false; while (bw->parent) bw = bw->parent; if (!browser_window_check_throbber(bw)) gui_window_stop_throbber(bw->window); } bool browser_window_check_throbber(struct browser_window *bw) { int children, index; if (bw->throbbing) return true; if (bw->children) { children = bw->rows * bw->cols; for (index = 0; index < children; index++) { if (browser_window_check_throbber(&bw->children[index])) return true; } } if (bw->iframes) { for (index = 0; index < bw->iframe_count; index++) { if (browser_window_check_throbber(&bw->iframes[index])) return true; } } return false; } /** * when ready, set icon at top level * \param bw browser_window * current implementation ignores lower-levels' link rels completely */ void browser_window_set_icon(struct browser_window *bw) { while (bw->parent) bw = bw->parent; if (bw->current_content != NULL && content_get_type(bw->current_content) == CONTENT_HTML) gui_window_set_icon(bw->window, html_get_favicon(bw->current_content)); else gui_window_set_icon(bw->window, NULL); } /** * Redraw browser window, set extent to content, and update title. * * \param bw browser_window * \param scroll_to_top move view to top of page */ void browser_window_update(struct browser_window *bw, bool scroll_to_top) { int x, y; if (bw->current_content == NULL) return; switch (bw->browser_window_type) { default: /* Fall through to normal * (frame(set)s aren't handled by the core yet) */ case BROWSER_WINDOW_NORMAL: /* Root browser window, constituting a front end window/tab */ gui_window_set_title(bw->window, content_get_title(bw->current_content)); browser_window_update_extent(bw); if (scroll_to_top) gui_window_set_scroll(bw->window, 0, 0); /* if frag_id exists, then try to scroll to it */ /** \TODO don't do this if the user has scrolled */ if (bw->frag_id && html_get_id_offset(bw->current_content, bw->frag_id, &x, &y)) { gui_window_set_scroll(bw->window, x, y); } gui_window_redraw_window(bw->window); break; case BROWSER_WINDOW_IFRAME: /* Internal iframe browser window */ /** \TODO handle scrollbar extents, scroll offset */ html_redraw_a_box(bw->parent->current_content, bw->box); break; } } void browser_window_update_box(struct browser_window *bw, const union content_msg_data *data) { int pos_x; int pos_y; union content_msg_data data_copy = *data; struct browser_window *top; switch (bw->browser_window_type) { default: /* fall through for frame(set)s, * until they are handled by core */ case BROWSER_WINDOW_NORMAL: gui_window_update_box(bw->window, data); break; case BROWSER_WINDOW_IFRAME: browser_window_get_position(bw, true, &pos_x, &pos_y); top = bw; while (top && !top->window && top->parent) { top = top->parent; } /* TODO: update gui_window_update_box so it takes a struct rect * instead of msg data. */ data_copy.redraw.x += pos_x; data_copy.redraw.y += pos_y; data_copy.redraw.object_x += pos_x; data_copy.redraw.object_y += pos_y; gui_window_update_box(top->window, &data_copy); break; } } /** * Stop all fetching activity in a browser window. * * \param bw browser window */ void browser_window_stop(struct browser_window *bw) { int children, index; if (bw->loading_content != NULL) { hlcache_handle_abort(bw->loading_content); hlcache_handle_release(bw->loading_content); bw->loading_content = NULL; } if (bw->current_content != NULL && content_get_status( bw->current_content) != CONTENT_STATUS_DONE) { nserror error; assert(content_get_status(bw->current_content) == CONTENT_STATUS_READY); error = hlcache_handle_abort(bw->current_content); assert(error == NSERROR_OK); } schedule_remove(browser_window_refresh, bw); if (bw->children) { children = bw->rows * bw->cols; for (index = 0; index < children; index++) browser_window_stop(&bw->children[index]); } if (bw->iframes) { children = bw->iframe_count; for (index = 0; index < children; index++) browser_window_stop(&bw->iframes[index]); } if (bw->current_content != NULL) { browser_window_refresh_url_bar(bw, content_get_url(bw->current_content), bw->frag_id); } browser_window_stop_throbber(bw); } /** * Reload the page in a browser window. * * \param bw browser window * \param all whether to reload all objects associated with the page */ void browser_window_reload(struct browser_window *bw, bool all) { hlcache_handle *c; unsigned int i; if (bw->current_content == NULL || bw->loading_content != NULL) return; if (all && content_get_type(bw->current_content) == CONTENT_HTML) { struct html_stylesheet *sheets; struct content_html_object *object; unsigned int count; c = bw->current_content; /* invalidate objects */ object = html_get_objects(c, &count); for (; object != NULL; object = object->next) { if (object->content != NULL) content_invalidate_reuse_data(object->content); } /* invalidate stylesheets */ sheets = html_get_stylesheets(c, &count); for (i = STYLESHEET_START; i != count; i++) { if (sheets[i].type == HTML_STYLESHEET_EXTERNAL && sheets[i].data.external != NULL) { content_invalidate_reuse_data( sheets[i].data.external); } } } content_invalidate_reuse_data(bw->current_content); browser_window_go(bw, content_get_url(bw->current_content), 0, false); } /** * Change the status bar of a browser window. * * \param bw browser window * \param text new status text (copied) */ void browser_window_set_status(struct browser_window *bw, const char *text) { int text_len; /* find topmost window */ while (bw->parent) bw = bw->parent; if ((bw->status_text != NULL) && (strcmp(text, bw->status_text) == 0)) { /* status text is unchanged */ bw->status_match++; return; } /* status text is changed */ text_len = strlen(text); if ((bw->status_text == NULL) || (bw->status_text_len < text_len)) { /* no current string allocation or it is not long enough */ free(bw->status_text); bw->status_text = strdup(text); bw->status_text_len = text_len; } else { /* current allocation has enough space */ memcpy(bw->status_text, text, text_len + 1); } bw->status_miss++; gui_window_set_status(bw->window, bw->status_text); } /** * Change the shape of the mouse pointer * * \param shape shape to use */ void browser_window_set_pointer(struct browser_window *bw, gui_pointer_shape shape) { struct browser_window *root = bw; while (root && !root->window && root->parent) { root = root->parent; } assert(root); assert(root->window); gui_window_set_pointer(root->window, shape); } /** * Close and destroy a browser window. * * \param bw browser window */ void browser_window_destroy(struct browser_window *bw) { /* can't destoy child windows on their own */ assert(!bw->parent); /* destroy */ browser_window_destroy_internal(bw); free(bw); } /** * Close and destroy all child browser window. * * \param bw browser window */ void browser_window_destroy_children(struct browser_window *bw) { int i; if (bw->children) { for (i = 0; i < (bw->rows * bw->cols); i++) browser_window_destroy_internal(&bw->children[i]); free(bw->children); bw->children = NULL; bw->rows = 0; bw->cols = 0; } if (bw->iframes) { for (i = 0; i < bw->iframe_count; i++) browser_window_destroy_internal(&bw->iframes[i]); free(bw->iframes); bw->iframes = NULL; bw->iframe_count = 0; } } /** * Release all memory associated with a browser window. * * \param bw browser window */ void browser_window_destroy_internal(struct browser_window *bw) { assert(bw); LOG(("Destroying window")); if (bw->children != NULL || bw->iframes != NULL) browser_window_destroy_children(bw); schedule_remove(browser_window_refresh, bw); /* If this brower window is not the root window, and has focus, unset * the root browser window's focus pointer. */ if (!bw->window) { struct browser_window *top = bw; while (top && !top->window && top->parent) top = top->parent; if (top->focus == bw) top->focus = NULL; } /* Destruction order is important: we must ensure that the frontend * destroys any window(s) associated with this browser window before * we attempt any destructive cleanup. */ if (bw->window) { /* Only the root window has a GUI window */ gui_window_destroy(bw->window); } if (bw->loading_content != NULL) { hlcache_handle_release(bw->loading_content); bw->loading_content = NULL; } if (bw->current_content != NULL) { content_status status = content_get_status(bw->current_content); if (status == CONTENT_STATUS_READY || status == CONTENT_STATUS_DONE) content_close(bw->current_content); hlcache_handle_release(bw->current_content); bw->current_content = NULL; } if (bw->box != NULL) { bw->box->iframe = NULL; bw->box = NULL; } /* TODO: After core FRAMES are done, should be * if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) */ if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) { selection_destroy(bw->sel); } /* These simply free memory, so are safe here */ history_destroy(bw->history); free(bw->name); free(bw->frag_id); free(bw->status_text); bw->status_text = NULL; LOG(("Status text cache match:miss %d:%d", bw->status_match, bw->status_miss)); } /** * Returns the browser window that is responsible for the child. * * \param bw The browser window to find the owner of * \return the browser window's owner */ struct browser_window *browser_window_owner(struct browser_window *bw) { /* an iframe's parent is just the parent window */ if (bw->browser_window_type == BROWSER_WINDOW_IFRAME) return bw->parent; /* the parent of a frameset is either a NORMAL window or an IFRAME */ while (bw->parent != NULL) { switch (bw->browser_window_type) { case BROWSER_WINDOW_NORMAL: case BROWSER_WINDOW_IFRAME: return bw; case BROWSER_WINDOW_FRAME: case BROWSER_WINDOW_FRAMESET: bw = bw->parent; break; } } return bw; } /** * Reformat a browser window contents to a new width or height. * * \param bw the browser window to reformat * \param width new width * \param height new height */ void browser_window_reformat(struct browser_window *bw, int width, int height) { hlcache_handle *c = bw->current_content; if (c == NULL) return; content_reformat(c, width / bw->scale, height / bw->scale); } /** * Sets the scale of a browser window * * \param bw The browser window to scale * \param scale The new scale * \param all Scale all windows in the tree (ie work up aswell as down) */ void browser_window_set_scale(struct browser_window *bw, float scale, bool all) { while (bw->parent && all) bw = bw->parent; browser_window_set_scale_internal(bw, scale); if (bw->parent) bw = bw->parent; browser_window_recalculate_frameset(bw); } void browser_window_set_scale_internal(struct browser_window *bw, float scale) { int i; hlcache_handle *c; if (fabs(bw->scale-scale) < 0.0001) return; bw->scale = scale; c = bw->current_content; if (c != NULL) { if (content_can_reformat(c) == false) { browser_window_update(bw, false); } else { bw->reformat_pending = true; browser_reformat_pending = true; } } gui_window_set_scale(bw->window, scale); for (i = 0; i < (bw->cols * bw->rows); i++) browser_window_set_scale_internal(&bw->children[i], scale); for (i = 0; i < bw->iframe_count; i++) browser_window_set_scale_internal(&bw->iframes[i], scale); } /** * Update URL bar for a given browser window to given URL * * \param bw Browser window to update URL bar for. * \param url URL for content displayed by bw, excluding any fragment. * \param frag Additional fragment. May be NULL if none. */ void browser_window_refresh_url_bar(struct browser_window *bw, const char *url, const char *frag) { char *url_buf; assert(bw); assert(url); bw->visible_select_menu = NULL; if (bw->parent != NULL) { /* Not root window; don't set a URL in GUI URL bar */ return; } if (frag == NULL) { /* With no fragment, we may as well pass url straight through * saving a malloc, copy, free cycle. */ gui_window_set_url(bw->window, url); } else { url_buf = malloc(strlen(url) + 1 /* # */ + strlen(frag) + 1 /* \0 */); if (url_buf != NULL) { /* This sprintf is safe because of the above size * calculation, thus we don't need snprintf */ sprintf(url_buf, "%s#%s", url, frag); gui_window_set_url(bw->window, url_buf); free(url_buf); } else { warn_user("NoMemory", 0); } } } /** * Locate a browser window in the specified stack according. * * \param bw the browser_window to search all relatives of * \param target the target to locate * \param new_window always return a new window (ie 'Open Link in New Window') */ struct browser_window *browser_window_find_target(struct browser_window *bw, const char *target, browser_mouse_state mouse) { struct browser_window *bw_target; struct browser_window *top; hlcache_handle *c; int rdepth; /* use the base target if we don't have one */ c = bw->current_content; if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML) target = html_get_base_target(c); if (target == NULL) target = TARGET_SELF; /* allow the simple case of target="_blank" to be ignored if requested */ if ((!(mouse & BROWSER_MOUSE_CLICK_2)) && (!((mouse & BROWSER_MOUSE_CLICK_2) && (mouse & BROWSER_MOUSE_MOD_2))) && (!option_target_blank)) { /* not a mouse button 2 click * not a mouse button 1 click with ctrl pressed * configured to ignore target="_blank" */ if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank"))) return bw; } /* handle reserved keywords */ if (((option_button_2_tab) && (mouse & BROWSER_MOUSE_CLICK_2)) || ((!option_button_2_tab) && ((mouse & BROWSER_MOUSE_CLICK_1) && (mouse & BROWSER_MOUSE_MOD_2))) || ((option_button_2_tab) && ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank"))))) { /* open in new tab if: * - button_2 opens in new tab and button_2 was pressed * OR * - button_2 doesn't open in new tabs and button_1 was * pressed with ctrl held * OR * - button_2 opens in new tab and the link target is "_blank" */ bw_target = browser_window_create(NULL, bw, NULL, false, true); if (!bw_target) return bw; return bw_target; } else if (((!option_button_2_tab) && (mouse & BROWSER_MOUSE_CLICK_2)) || ((option_button_2_tab) && ((mouse & BROWSER_MOUSE_CLICK_1) && (mouse & BROWSER_MOUSE_MOD_2))) || ((!option_button_2_tab) && ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank"))))) { /* open in new window if: * - button_2 doesn't open in new tabs and button_2 was pressed * OR * - button_2 opens in new tab and button_1 was pressed with * ctrl held * OR * - button_2 doesn't open in new tabs and the link target is * "_blank" */ bw_target = browser_window_create(NULL, bw, NULL, false, false); if (!bw_target) return bw; return bw_target; } else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) { return bw; } else if ((target == TARGET_PARENT) || (!strcasecmp(target, "_parent"))) { if (bw->parent) return bw->parent; return bw; } else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) { while (bw->parent) bw = bw->parent; return bw; } /* find frame according to B.8, ie using the following priorities: * * 1) current frame * 2) closest to front */ rdepth = -1; bw_target = NULL; for (top = bw; top->parent; top = top->parent); browser_window_find_target_internal(top, target, 0, bw, &rdepth, &bw_target); if (bw_target) return bw_target; /* we require a new window using the target name */ if (!option_target_blank) return bw; bw_target = browser_window_create(NULL, bw, NULL, false, false); if (!bw_target) return bw; /* frame names should begin with an alphabetic character (a-z,A-Z), * however in practice you get things such as '_new' and '2left'. The * only real effect this has is when giving out names as it can be * assumed that an author intended '_new' to create a new nameless * window (ie '_blank') whereas in the case of '2left' the intention * was for a new named window. As such we merely special case windows * that begin with an underscore. */ if (target[0] != '_') { bw_target->name = strdup(target); if (!bw_target->name) warn_user("NoMemory", 0); } return bw_target; } void browser_window_find_target_internal(struct browser_window *bw, const char *target, int depth, struct browser_window *page, int *rdepth, struct browser_window **bw_target) { int i; if ((bw->name) && (!strcasecmp(bw->name, target))) { if ((bw == page) || (depth > *rdepth)) { *rdepth = depth; *bw_target = bw; } } if ((!bw->children) && (!bw->iframes)) return; depth++; if (bw->children != NULL) { for (i = 0; i < (bw->cols * bw->rows); i++) { if ((bw->children[i].name) && (!strcasecmp(bw->children[i].name, target))) { if ((page == &bw->children[i]) || (depth > *rdepth)) { *rdepth = depth; *bw_target = &bw->children[i]; } } if (bw->children[i].children) browser_window_find_target_internal( &bw->children[i], target, depth, page, rdepth, bw_target); } } if (bw->iframes != NULL) { for (i = 0; i < bw->iframe_count; i++) browser_window_find_target_internal(&bw->iframes[i], target, depth, page, rdepth, bw_target); } } /** * Handle mouse movements in a browser window. * * \param bw browser window * \param mouse state of mouse buttons and modifier keys * \param x coordinate of mouse * \param y coordinate of mouse */ void browser_window_mouse_track(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { hlcache_handle *c = bw->current_content; if (c == NULL && bw->drag_type != DRAGGING_FRAME) return; /* detect end of drag operation in case the platform-specific code doesn't call browser_mouse_drag_end() (RISC OS code does) */ if (bw->drag_type != DRAGGING_NONE && !mouse) { browser_window_mouse_drag_end(bw, mouse, x, y); } if (bw->drag_type != DRAGGING_NONE) { selection_set_browser_window(bw->sel, bw); } if (bw->drag_type == DRAGGING_FRAME) { browser_window_resize_frame(bw, bw->x0 + x, bw->y0 + y); } else if (bw->drag_type == DRAGGING_PAGE_SCROLL) { /* mouse movement since drag started */ int scrollx = bw->drag_start_x - x; int scrolly = bw->drag_start_y - y; /* new scroll offsets */ scrollx += bw->drag_start_scroll_x; scrolly += bw->drag_start_scroll_y; bw->drag_start_scroll_x = scrollx; bw->drag_start_scroll_y = scrolly; switch (bw->browser_window_type) { default: /* Fall through to normal, until frame(set)s are * handled in the core */ case BROWSER_WINDOW_NORMAL: gui_window_set_scroll(bw->window, scrollx, scrolly); break; case BROWSER_WINDOW_IFRAME: /* TODO */ break; } } else { assert(c != NULL); content_mouse_track(c, bw, mouse, x, y); } } /** * Handle mouse clicks in a browser window. * * \param bw browser window * \param mouse state of mouse buttons and modifier keys * \param x coordinate of mouse * \param y coordinate of mouse */ void browser_window_mouse_click(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { hlcache_handle *c = bw->current_content; struct browser_window *top; if (!c) return; /* Set focus browser window */ top = bw; while (top && !top->window && top->parent) top = top->parent; top->focus = bw; selection_set_browser_window(bw->sel, bw); switch (content_get_type(c)) { case CONTENT_HTML: case CONTENT_TEXTPLAIN: content_mouse_action(c, bw, mouse, x, y); break; default: if (mouse & BROWSER_MOUSE_MOD_2) { if (mouse & BROWSER_MOUSE_DRAG_2) gui_drag_save_object(GUI_SAVE_OBJECT_NATIVE, c, bw->window); else if (mouse & BROWSER_MOUSE_DRAG_1) gui_drag_save_object(GUI_SAVE_OBJECT_ORIG, c, bw->window); } else if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { browser_window_page_drag_start(bw, x, y); browser_window_set_pointer(bw, GUI_POINTER_MOVE); } break; } } /** * Handles the end of a drag operation in a browser window. * * \param bw browser window * \param mouse state of mouse buttons and modifier keys * \param x coordinate of mouse * \param y coordinate of mouse * * TODO: MOVE content specific stuff out */ void browser_window_mouse_drag_end(struct browser_window *bw, browser_mouse_state mouse, int x, int y) { if (bw->visible_select_menu != NULL) { form_select_mouse_drag_end(bw->visible_select_menu, mouse, x, y); return; } if (bw->scrollbar != NULL) { html_overflow_scroll_drag_end(bw->scrollbar, mouse, x, y); return; } switch (bw->drag_type) { case DRAGGING_SELECTION: { hlcache_handle *h = bw->current_content; if (h) { int dir = -1; size_t idx; if (selection_dragging_start(bw->sel)) dir = 1; if (content_get_type(h) == CONTENT_HTML) { idx = html_selection_drag_end(h, mouse, x, y, dir); if (idx != 0) selection_track(bw->sel, mouse, idx); } else { assert(content_get_type(h) == CONTENT_TEXTPLAIN); idx = textplain_offset_from_coords(h, x, y, dir); selection_track(bw->sel, mouse, idx); } } selection_drag_end(bw->sel); } break; default: break; } bw->drag_type = DRAGGING_NONE; } /** * Redraw a rectangular region of a browser window * * \param bw browser window to be redrawn * \param x x co-ord of top-left * \param y y co-ord of top-left * \param width width of rectangle * \param height height of rectangle */ void browser_window_redraw_rect(struct browser_window *bw, int x, int y, int width, int height) { content_request_redraw(bw->current_content, x, y, width, height); } /** * Start drag scrolling the contents of the browser window * * \param bw browser window * \param x x ordinate of initial mouse position * \param y y ordinate */ void browser_window_page_drag_start(struct browser_window *bw, int x, int y) { bw->drag_type = DRAGGING_PAGE_SCROLL; bw->drag_start_x = x; bw->drag_start_y = y; switch (bw->browser_window_type) { default: /* fall through until frame(set)s are handled in core */ case BROWSER_WINDOW_NORMAL: gui_window_get_scroll(bw->window, &bw->drag_start_scroll_x, &bw->drag_start_scroll_y); gui_window_scroll_start(bw->window); break; case BROWSER_WINDOW_IFRAME: /* TODO */ break; } } /** * Check availability of Back action for a given browser window * * \param bw browser window * \return true if Back action is available */ bool browser_window_back_available(struct browser_window *bw) { return (bw && bw->history && history_back_available(bw->history)); } /** * Check availability of Forward action for a given browser window * * \param bw browser window * \return true if Forward action is available */ bool browser_window_forward_available(struct browser_window *bw) { return (bw && bw->history && history_forward_available(bw->history)); } /** * Check availability of Reload action for a given browser window * * \param bw browser window * \return true if Reload action is available */ bool browser_window_reload_available(struct browser_window *bw) { return (bw && bw->current_content && !bw->loading_content); } /** * Check availability of Stop action for a given browser window * * \param bw browser window * \return true if Stop action is available */ bool browser_window_stop_available(struct browser_window *bw) { return (bw && (bw->loading_content || (bw->current_content && (content_get_status(bw->current_content) != CONTENT_STATUS_DONE)))); }